Merge "SystemUI: Fix carrier customization emergency button not shown" into r-keystone-qcom-dev
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 6836bea..60af186 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2865,6 +2865,9 @@
                 return true;
             }
             return false;
+        } else if (profile == BluetoothProfile.GROUP_CLIENT) {
+            BluetoothDeviceGroup groupClient = new BluetoothDeviceGroup(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -2954,6 +2957,10 @@
                 BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy;
                 hearingAid.close();
                 break;
+            case BluetoothProfile.GROUP_CLIENT:
+                BluetoothDeviceGroup groupClient = (BluetoothDeviceGroup) proxy;
+                groupClient.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothDeviceGroup.java b/core/java/android/bluetooth/BluetoothDeviceGroup.java
new file mode 100644
index 0000000..0a0fea5
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDeviceGroup.java
@@ -0,0 +1,852 @@
+/******************************************************************************
+ *  Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *      * Neither the name of The Linux Foundation nor the names of its
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.IBluetoothGroupCallback;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+
+/**
+ * This class provides the public APIs to perform operations of
+ * the Group Identification Profile.
+ *
+ * <p> This class provides functionalities to enable communication with remote
+ * devices which are grouped together to achieve common use cases in
+ * synchronized manner.
+ * <p> BluetoothDeviceGroup is a proxy object for controlling the Bluetooth Group
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothDeviceGroup
+ * proxy object. Use {@link BluetoothAdapter#closeProfileProxy} to close connection
+ * of the BluetoothDeviceGroup proxy object with the profile service.
+ * <p> BluetoothDeviceGroup proxy object can be used to identify and fetch Device Group.
+ * Also, API’s are exposed to get exclusive access of group devices for critical
+ * operations. Implement BluetoothGroupCallback to get results invoked API's.
+ *
+ * @hide
+ */
+
+
+public final class BluetoothDeviceGroup implements BluetoothProfile {
+    private static final String TAG = "BluetoothDeviceGroup";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /** Group Client App is registerd for callbacks successfully */
+    public static final int APP_REGISTRATION_SUCCESSFUL = 0;
+    /** Group Client App registration failed for callbacks */
+    public static final int APP_REGISTRATION_FAILED = 1;
+
+    /** Group Discovery Status when discovery is started */
+    public static final int GROUP_DISCOVERY_STARTED = 0x00;
+
+    /** Group Discovery Status when discovery is stopped */
+    public static final int GROUP_DISCOVERY_STOPPED = 0x01;
+
+    /** When Application starts Group discovery */
+    public static final int DISCOVERY_STARTED_BY_APPL = 0x00;
+
+    /** When Application stops Group discovery */
+    public static final int DISCOVERY_STOPPED_BY_APPL = 0x01;
+
+    /** When Group discovery is started as a result of
+     * change in Group property. */
+    public static final int DISCOVERY_STARTED_GROUP_PROP_CHANGED = 0x02;
+
+    /** When all devices of Group are discovered */
+    public static final int DISCOVERY_COMPLETED = 0x03;
+
+    /** Group discovery by timeeut. Group device not found in 10 sec. */
+    public static final int DISCOVERY_STOPPED_BY_TIMEOUT = 0x04;
+
+    /** Invalid params are provided for Group discovery */
+    public static final int DISCOVERY_NOT_STARTED_INVALID_PARAMS = 0x05;
+
+    /** Value to release Exclusive Access */
+    public static final int ACCESS_RELEASED = 0x01;
+
+    /** Value to acquire Exclusive Access */
+    public static final int ACCESS_GRANTED = 0x02;
+
+    /** When exclusive access is changed to #ACCESS_RELEASED for all reqested Group devices */
+    public static final int EXCLUSIVE_ACCESS_RELEASED = 0x00;
+
+    /** When exclusive access of the Group device is changed to #ACCESS_RELEASED by timeout */
+    public static final int EXCLUSIVE_ACCESS_RELEASED_BY_TIMEOUT = 0x01;
+
+    /** When exclusive access of all requested Group devices is changed to #ACCESS_GRANTED */
+    public static final int ALL_DEVICES_GRANTED_ACCESS = 0x02;
+
+    /** When exclusive access of some of the requested Group devices is changed to #ACCESS_GRANTED
+      * because of timeout in #setExclusiveAccess operation */
+    public static final int SOME_GRANTED_ACCESS_REASON_TIMEOUT = 0x03;
+
+    /** When access value of some of the requested Group devices is changed to #ACCESS_GRANTED
+      * because some of the Group devices were disconnected */
+    public static final int SOME_GRANTED_ACCESS_REASON_DISCONNECTION = 0x04;
+
+    /** When Exclusive Access couldnt be fetched as one of the Group devices denied
+      * to set value to #ACCESS_DENIED*/
+    public static final int ACCESS_DENIED = 0x05;
+
+    /** Suggests that invalid parameters are passed in #setExclusiveAccess request*/
+    public static final int INVALID_ACCESS_REQ_PARAMS = 0x06;
+
+    /** Invalid Group ID */
+    public static final int INVALID_GROUP_ID = 0x10;
+
+    /** MIN GROUP_ID Value*/
+    public static final int GROUP_ID_MIN = 0x00;
+    /** MAX GROUP_ID Value*/
+    public static final int GROUP_ID_MAX = 0x0F;
+
+    /** Invalid APP ID */
+    public static final int INVALID_APP_ID = 0x10;
+
+    /** MIN APP_ID Value*/
+    public static final int APP_ID_MIN = 0x00;
+    /** MAX APP_ID Value*/
+    public static final int APP_ID_MAX = 0x0F;
+
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+                "android.bluetooth.group.profile.action.CONNECTION_STATE_CHANGED";
+
+    private int mAppId;
+    private boolean mAppRegistered = false;
+    private Handler mHandler;
+    private BluetoothGroupCallback mCallback;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothDeviceGroup> mProfileConnector =
+        new BluetoothProfileConnector(this, BluetoothProfile.GROUP_CLIENT,
+                "BluetoothDeviceGroup", IBluetoothDeviceGroup.class.getName()) {
+            @Override
+            public IBluetoothDeviceGroup getServiceInterface(IBinder service) {
+                return IBluetoothDeviceGroup.Stub.asInterface(Binder.allowBlocking(service));
+            }
+        };
+
+    /**
+     * Creates a BluetoothDeviceGroup proxy object for interacting with the local
+     * Bluetooth Service which handles Group operations.
+     * @hide
+     */
+    /*package*/ BluetoothDeviceGroup(Context context, ServiceListener listener) {
+        mProfileConnector.connect(context, listener);
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+    }
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+               new IBluetoothStateChangeCallback.Stub() {
+                   public void onBluetoothStateChange(boolean up) {
+                       if (!up) {
+                           mAppRegistered = false;
+                       }
+                   }
+               };
+
+    /**
+     * Close this BluetoothGroupDevice client object.
+     *
+     * Application should call this method as soon as it is done with
+     * Group operations.
+     */
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+
+        mAppRegistered = false;
+        final IBluetoothDeviceGroup service = getService();
+        if (service != null) {
+            try {
+                service.unregisterGroupClientApp(mAppId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            }
+        }
+
+        mProfileConnector.disconnect();
+    }
+
+    /**
+     * @hide
+     */
+    private IBluetoothDeviceGroup getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void finalize() {
+        close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+
+        return null;
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null &&
+                BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    private final IBluetoothGroupCallback.Stub mBluetoothGroupCallback =
+            new IBluetoothGroupCallback.Stub() {
+
+        @Override
+        public void onGroupClientAppRegistered(int status, int appId) {
+            if (DBG) {
+                Log.d(TAG, "onGroupClientAppRegistered() - status=" + status
+                        + " appId = " + appId);
+            }
+
+            if (status != APP_REGISTRATION_SUCCESSFUL) {
+              mAppRegistered = false;
+            }
+
+            mAppId = appId;
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onGroupClientAppRegistered(status, appId);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onGroupClientAppUnregistered(int status) {
+            if (DBG) {
+                Log.d(TAG, "onGroupClientAppUnregistered() - status=" + status
+                        + " mAppId=" + mAppId);
+            }
+        }
+
+        @Override
+        public void onConnectionStateChanged (int state, BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "onConnectionStateChanged() - state = " + state
+                        + " device = " + device);
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onConnectionStateChanged(state, device);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onNewGroupFound(int groupId, BluetoothDevice device,
+                ParcelUuid uuid) {
+            if (DBG) {
+                Log.d(TAG, "onNewGroupFound() - appId = " + mAppId +
+                        ", groupId = " + groupId + ", device: " + device +
+                        ", Including service UUID: " + uuid.toString());
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onNewGroupFound(groupId, device, uuid.getUuid());
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onGroupDiscoveryStatusChanged(int groupId, int status, int reason) {
+            if (DBG) {
+                Log.d(TAG, "onGroupDiscoveryStatusChanged() - appId = " + mAppId +
+                        ", groupId = " + groupId + ", status: " + status +
+                        ", reason: " + reason);
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onGroupDiscoveryStatusChanged(groupId, status, reason);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onGroupDeviceFound(int groupId, BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "onGroupDeviceFound() - appId = " + mAppId + ", device = " + device);
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onGroupDeviceFound(groupId, device);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onExclusiveAccessChanged(int groupId, int value, int status,
+                List<BluetoothDevice> devices) {
+            if (DBG) {
+                Log.d(TAG, "onExclusiveAccessChanged() - appId = " + mAppId
+                        + ", groupId = " + groupId + ", value = " + value
+                        + " accessStatus = " + status + ", devices: " + devices);
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onExclusiveAccessChanged(groupId, value, status, devices);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onExclusiveAccessStatusFetched(int groupId, int accessValue) {
+        }
+
+        @Override
+        public void onExclusiveAccessAvailable (int groupId, BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "onExclusiveAccessAvailable() - appId = " + mAppId
+                        + ", groupId = " + groupId + ", device: " + device);
+            }
+
+            runOrQueueCallback(new Runnable() {
+                @Override
+                public void run() {
+                    final BluetoothGroupCallback callback = mCallback;
+                    if (callback != null) {
+                        callback.onExclusiveAccessAvailable(groupId, device);
+                    }
+                }
+            });
+        }
+    };
+
+    /**
+     * Registers callbacks to be received by application on completion of
+     * required operations.
+     *
+     * @param callbacks    Reference of BluetoothGroupCallback implemented in
+     *                     application.
+     * @param handler      handler that will receive asynchronous callbacks.
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean registerGroupClientApp(BluetoothGroupCallback callbacks, Handler handler) {
+        if (DBG) log("registerGroupClientApp() mAppRegistered = " + mAppRegistered);
+
+        /* Check if app is trying multiple registrations */
+        if (mAppRegistered) {
+            Log.e(TAG, "App already registered.");
+            return false;
+        }
+
+        mHandler = handler;
+        mCallback = callbacks;
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy not attached to Profile Service. Can't register App.");
+            return false;
+        }
+
+        mAppRegistered = true;
+        try {
+            UUID uuid = UUID.randomUUID();
+            service.registerGroupClientApp(new ParcelUuid(uuid), mBluetoothGroupCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+     }
+
+    /**
+     * Starts discovery of the remaining Group devices which are part of the group.
+     *
+     * <p> This API should be called when onNewGroupFound() is received in the
+     * application and when given group is the required device group. This
+     * API can also be used to rediscover the undiscovered Group devices.
+     *
+     * <p> To the application that started group discovery,
+     * {@link BluetoothGroupCallback#onGroupDeviceFound} callback will be given when
+     * a new Group device is found and {@link BluetoothGroupCallback#onGroupDiscoveryStatusChanged}
+     * callback will be given when discovery is started.
+     *
+     * @param groupId    Identifier of the Group for which group
+     *                 discovery has to be started.
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean startGroupDiscovery(int groupId) {
+        if (DBG) log("startGroupDiscovery() : groupId = " + groupId);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't start group discovery");
+            return false;
+        }
+
+        try {
+            UUID uuid = UUID.randomUUID();
+            service.startGroupDiscovery(mAppId ,groupId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    /**
+     * Stops ongoing group discovery for Group identified by groupId.
+     *
+     * <p> {@link BluetoothGroupCallback#onGroupDiscoveryStatusChanged} is given
+     * when group discovery is stopped.
+     *
+     * @param groupId  Identifier of the Group for which group
+     *                 discovery has to be stopped.
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean stopGroupDiscovery(int groupId) {
+        if (DBG) log("stopGroupDiscovery() : groupId = " + groupId);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't Stop group discovery");
+            return false;
+        }
+
+        try {
+            service.stopGroupDiscovery(mAppId ,groupId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    /**
+     * Fetches already discovered Groups.
+     *
+     * @return    List of DeviceGroup that are already discovered.
+     */
+    public List<DeviceGroup> getDiscoveredGroups() {
+        return getDiscoveredGroups(false);
+    }
+
+    /**
+     * Fetches already discovered device groups.
+     *
+     * @param mPublicAddr  All discovered device groups with public address of devices.
+     * @return    List of Device Groups that are already discovered.
+     * @hide
+     */
+    public List<DeviceGroup> getDiscoveredGroups(boolean mPublicAddr) {
+        if (DBG) log("getDiscoveredGroups()");
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return null;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't fetch Groups.");
+            return null;
+        }
+
+        try {
+            List<DeviceGroup> groups = service.getDiscoveredGroups(mPublicAddr);
+            return groups;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+
+        return null;
+    }
+
+    /**
+     * Fetch details of a already discovered Group identified by groupId.
+     *
+     * @param groupId Identifier of the Group for which Group details are required.
+     * @return        Required DeviceGroup.
+     */
+    public DeviceGroup getGroup(int groupId) {
+        return getGroup(groupId, false);
+    }
+
+    /**
+     * Fetch details of a already discovered Group identified by groupId.
+     *
+     * @param groupId       Identifier of the device group for which group
+     *                      details are required.
+     * @param mPublicAddr   DeviceGroup with Public Address of the group devices.
+     * @return              Required DeviceGroup.
+     * @hide
+     */
+    public DeviceGroup getGroup(int groupId, boolean mPublicAddr) {
+        if (DBG) log("getGroup() : groupId = " + groupId);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return null;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't fetch Group.");
+            return null;
+        }
+
+        try {
+            DeviceGroup group = service.getDeviceGroup(groupId, mPublicAddr);
+            return group;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+
+        return null;
+    }
+
+    /**
+     * Get Group Identifier of the remote device to which it belongs.
+     *
+     * @param device        BluetoothDevice instance of the remote device.
+     * @param uuid          ParcelUuid of the primary service in which this
+     *                      Group Service is included.
+     * @return              Group identifier of the required device.
+     */
+    public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid) {
+        return getRemoteDeviceGroupId(device, uuid, false);
+    }
+
+    /**
+     * Get Group Identifier of the remote device to which it belongs.
+     *
+     * @param device        BluetoothDevice instance of the remote device.
+     * @param uuid          ParcelUuid of the primary service in which this
+     *                      Group Service is included.
+     * @param mPublicAddr   Suggests that group identifier is required for passed
+     *                      public address of the remote device.
+     * @return              Group identifier of the required group for the device
+     * @hide
+     */
+    public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid,
+            boolean mPublicAddr) {
+        if (DBG) log("getRemoteDeviceGroupId() : device = " + device);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return INVALID_GROUP_ID;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service." +
+                    "Can't get group id for device.");
+            return INVALID_GROUP_ID;
+        }
+
+        try {
+            return service.getRemoteDeviceGroupId(device, uuid, mPublicAddr);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return INVALID_GROUP_ID;
+    }
+
+    /**
+     * Suggests whether discovery for a given Group is ongoing.
+     *
+     * @param groupId  Identifier of the Group for which discovery
+     *                 status is to be known.
+     * @return         true, if group discovery is ongoing for mentioned group.
+     *                 Otherwise, false.
+     */
+    public boolean isGroupDiscoveryInProgress (int groupId) {
+        if (DBG) log("isGroupDiscoveryInProgress() : groupId = " + groupId);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service.Can't get discovery status.");
+            return false;
+        }
+
+        try {
+            return service.isGroupDiscoveryInProgress(groupId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Acquires/Releases exclusive access of a given Group or subgroup.
+     * The result of this operation is returned in
+     * {@link BluetoothGroupCallback#onExclusiveAccessChanged} callback.
+     *
+     * @param groupId  Identifier of the Group.
+     * @param devices  List of BluetoothDevice for which access has to be changed.
+     *                 If this parameter is passed as null, all Group devices in the
+     *                 mentioned group will be considered for request.
+     * @param value    Access which required to be changed.
+     *                 0x01 – Access released ({@link #ACCESS_RELEASED}).
+     *                 0x02 - Access granted ({@link #ACCESS_GRANTED}).
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean setExclusiveAccess(int groupId, List<BluetoothDevice> devices, int value) {
+        if (DBG) log("setExclusiveAccess() : groupId = " + groupId +
+                        ", access value: " + value);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't proceed.");
+            return false;
+        }
+
+        try {
+            service.setExclusiveAccess(mAppId, groupId, devices, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    /**
+     * Returns Status of the exclusive access for mentioned Group.
+     *
+     * @param groupId  Identifier of the Group.
+     * @param devices  List of BluetoothDevice for which access value has to be known.
+     *                 If this parameter is passed as null, all Group devices in the
+     *                 mentioned group will be queried for access status.
+     * @return true, if operation was initiated successfully.
+     * @hide
+     */
+    public boolean getExclusiveAccessStatus (int groupId, List<BluetoothDevice> devices) {
+        if (DBG) log("getExclusiveAccessStatus() : groupId = " + groupId);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service." +
+                    " Can't get exclusive access status.");
+            return false;
+        }
+
+        try {
+            service.getExclusiveAccessStatus(mAppId, groupId, devices);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    /**
+     * Creates GATT Connection with remote device for Group Operations.
+     *
+     * <p> This API acts as trigger to start service discovery to identify
+     * new device group on remote device once connection has been established
+     * successfully. Application calling connect will get
+     * {@link BluetoothGroupCallback#onNewGroupFoundcallback} after
+	 * {@link #onConnectionStateChanged} (once connection has been established
+	 * and group discovery is completed.)
+     *
+     * @param device  BluetoothDevice instance od remote device with which
+     *                Connection is required to be established.
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean connect (BluetoothDevice device) {
+        if (DBG) log("connect : device = " + device);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't connect.");
+            return false;
+        }
+
+        try {
+            service.connect(mAppId, device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    /**
+     * Initiates GATT disconnection for Group Operations.
+     *
+     * @param device  BluetoothDevice instance of remote device.
+     *                This API must be called if application is not
+     *                interested in any Group operations.
+     * @return true, if operation was initiated successfully.
+     */
+    public boolean disconnect (BluetoothDevice device) {
+        if (DBG) log("disconnect : device = " + device);
+
+        if (!mAppRegistered) {
+            Log.e(TAG, "App not registered for Group operations." +
+                    " Register App using registerGroupClientApp");
+            return false;
+        }
+
+        final IBluetoothDeviceGroup service = getService();
+        if (service == null) {
+            Log.e(TAG, "Proxy is not attached to Profile Service. Can't disconnect");
+            return false;
+        }
+
+        try {
+            service.disconnect(mAppId, device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+        return true;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    /**
+     * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
+     * immediately if no Handler was provided.
+     */
+    private void runOrQueueCallback(final Runnable cb) {
+        if (mHandler == null) {
+            try {
+                cb.run();
+            } catch (Exception ex) {
+                Log.w(TAG, "Unhandled exception in callback", ex);
+            }
+        } else {
+            mHandler.post(cb);
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGroupCallback.java b/core/java/android/bluetooth/BluetoothGroupCallback.java
new file mode 100644
index 0000000..a818cc7
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGroupCallback.java
@@ -0,0 +1,132 @@
+/******************************************************************************
+ *  Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *      * Neither the name of The Linux Foundation nor the names of its
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package android.bluetooth;
+
+import java.util.UUID;
+import java.util.List;
+/**
+ * This abstract class is used to implement {@link BluetoothDeviceGroup} callbacks.
+ * @hide
+ */
+public abstract class BluetoothGroupCallback {
+    /**
+     * This Callback gives connection state changed with specific group device.
+     *
+     * @param state Connection state of the {@link BluetoothProfile} group device.
+     * @param device Remote device for which connection state has changed.
+     */
+    public void onConnectionStateChanged (int state, BluetoothDevice device) {
+    }
+
+    /**
+     * This callback is given when application is registered for Group operation
+     * callbacks. This callback is given after {@link BluetoothDeviceGroup#registerGroupClientApp}
+     * is called.
+     *
+     * @param status Status of the group client app registration.
+     * @param appId Identifier of the application for group operations.
+     */
+    public void onGroupClientAppRegistered(int status, int appId) {
+    }
+
+    /**
+     * This callback is triggered when a new device group has been identified
+     * from one of the connected device. After this callback is received, application
+     * can choose to trigger discovery of device group using API
+     * {@link BluetoothDeviceGroup#startGroupDiscovery}
+     *
+     * @param groupId   Identifier of the Device Group.
+     * @param device  Remote device with which Device Group is found.
+     * @param uuid    UUID of the primary Service for this Device Group Service.
+     */
+    public void onNewGroupFound (int groupId,  BluetoothDevice device, UUID uuid) {
+    }
+
+    /**
+     * This Callback is triggered when device group discovery is either started/stopped.
+     *
+     * @param groupId   Identifier of the device group.
+     * @param status    Device Group Discovery status.
+	 *                  {@link BluetoothDeviceGroup#GROUP_DISCOVERY_STARTED}
+     *                  or  {@link BluetoothDeviceGroup#GROUP_DISCOVERY_STOPPED}.
+     * @param reason    Reason for change in the discovery status.
+     */
+    public void onGroupDiscoveryStatusChanged (int groupId, int status, int reason) {
+    }
+
+    /**
+     * This callback is triggered when new group device has been found after group
+     * discovery has been started. This callback is given on discovery of every
+     * new group device.
+     *
+     * @param groupId  Identifier of the device group.
+     * @param device  {@link BluetoothDevice} instance of discovered group device.
+     */
+    public void onGroupDeviceFound (int groupId, BluetoothDevice device) {
+    }
+
+    /**
+     * This callback is triggered after exclusive access status of the group
+     * or subgroup has been changed after the request from application.
+     *
+     * @param groupId   Identifier of the device group.
+     * @param value     Changed value of the exclusive access.
+     * @param status    Status associated with the exclusive access.
+     * @param devices   List of devices for which exclusive access has been changed.
+     */
+    public void onExclusiveAccessChanged (int groupId, int value, int status,
+            List<BluetoothDevice> devices) {
+    }
+
+    /**
+     * This callback gives access status of requested group/subgroup once
+     * it is fetched.
+     *
+     * @param groupId       Identifier of the device group.
+     * @param accessStatus  Value of the Exclusive Access.
+     */
+    public void onExclusiveAccessStatusFetched (int groupId, int accessStatus) {
+    }
+
+    /**
+     * This callback is given to application when exclusive access is available
+     * for the device of a given group for which was denied earlier.
+     * <p> Exclusive Access is considered available when group device sends notification
+     * for access changed to BluetoothDeviceGroup#ACCESS_RELEASED. This callback is
+     * given to the application which has requested the access earlier and the request
+     * had failed as one of the group device had DENIED the access.
+     *
+     * @param groupId  Identifier of the device group.
+     * @param device  {@link BluetoothDevice} which has exclusive access available.
+     */
+    public void onExclusiveAccessAvailable (int groupId, BluetoothDevice device) {
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 99c375c..0ab0ed25 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -212,12 +212,18 @@
     public static final int DUN = 22;
 
     /**
+     * Group Operation Profile (Client Role)
+     * @hide
+     */
+    public int GROUP_CLIENT = 23;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    int MAX_PROFILE_ID = 22;
+    int MAX_PROFILE_ID = 23;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/DeviceGroup.java b/core/java/android/bluetooth/DeviceGroup.java
new file mode 100644
index 0000000..f1bd2c3
--- /dev/null
+++ b/core/java/android/bluetooth/DeviceGroup.java
@@ -0,0 +1,175 @@
+/******************************************************************************
+ *  Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *      * Neither the name of The Linux Foundation nor the names of its
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Provides Device Group details.
+ *
+ * {@see BluetoothDeviceGroup}
+ * @hide
+ *
+ */
+
+public final class DeviceGroup implements Parcelable {
+    /** Identifier of the Device Group */
+    private int mGroupId;
+    /** Size of the Device Group. */
+    private int mSize;
+    /** List of all group devices {@link BluetoothDevice} */
+    private List <BluetoothDevice> mGroupDevices = new ArrayList<BluetoothDevice>();
+    /** Primary Service UUID which has included required Device Group service*/
+    private final ParcelUuid mIncludingSrvcUUID;
+    /** Suggests whether exclusive access can be taken for this device group */
+    private final boolean mExclusiveAccessSupport;
+
+    /**
+     * Constructor.
+     * @hide
+     */
+    public DeviceGroup(int groupId, int size, List<BluetoothDevice> groupDevices,
+            ParcelUuid includingSrvcUUID, boolean exclusiveAccessSupport) {
+        mGroupId = groupId;
+        mSize = size;
+        mGroupDevices = groupDevices;
+        mIncludingSrvcUUID = includingSrvcUUID;
+        mExclusiveAccessSupport = exclusiveAccessSupport;
+    }
+
+    public DeviceGroup(Parcel in) {
+        mGroupId = in.readInt();
+        mSize = in.readInt();
+        in.readList(mGroupDevices, BluetoothDevice.class.getClassLoader());
+        mIncludingSrvcUUID = in.readParcelable(ParcelUuid.class.getClassLoader());
+        mExclusiveAccessSupport = in.readBoolean();
+    }
+
+    /**
+     * Used to retrieve identifier of the Device Group.
+     *
+     * @return  Identifier of the Device Group.
+     */
+    public int getDeviceGroupId() {
+        return mGroupId;
+    }
+
+    /**
+     * Used to know total number group devices which are part of this Device Group.
+     *
+     * @return size of the Device Group
+     */
+    public int getDeviceGroupSize() {
+        return mSize;
+    }
+
+    /**
+     * Indicates total number of group devices discovered in Group Discovery procedure.
+     *
+     * @return total group devices discovered in the Device Group.
+     */
+    public int getTotalDiscoveredGroupDevices() {
+        return mGroupDevices.size();
+    }
+
+
+    /**
+     * Used to fetch group devices of the Device Group.
+     *
+     *@return List of group devices {@link BluetoothDevice} in the Device Group.
+     */
+    public List<BluetoothDevice> getDeviceGroupMembers() {
+        return mGroupDevices;
+    }
+
+    /**
+     * Suggests primary GATT service which has included this DeviceGroup Service
+     * for this device group. If remote device is part of multiple Device Groups then
+     * this uuid cant be null. If remote device is part of only one device froup
+     * then this returned parameter can be null.
+     *
+     *@return UUID of the GATT primary Service which has included this device group.
+     */
+    public ParcelUuid getIncludingServiceUUID() {
+        return mIncludingSrvcUUID;
+    }
+
+    /**
+     * Suggests whether exclusive access is supported by this Device Group.
+     *
+     * @return true, if exclusive access operation is supported by this Device Group.
+     * Otherwise, false.
+     */
+    public boolean isExclusiveAccessSupported() {
+        return mExclusiveAccessSupport;
+    }
+
+    /**
+     * Indicates whether all devices of this Device Group are discovered.
+     *
+     * @return true, if all group devices are discovered. Otherwise, false.
+     */
+    public boolean isGroupDiscoveredCompleted() {
+      return (mSize == getTotalDiscoveredGroupDevices());
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mGroupId);
+        dest.writeInt(mSize);
+        dest.writeList(mGroupDevices);
+        dest.writeParcelable(mIncludingSrvcUUID, 0);
+        dest.writeBoolean(mExclusiveAccessSupport);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<DeviceGroup> CREATOR =
+            new Parcelable.Creator<DeviceGroup>() {
+        public DeviceGroup createFromParcel(Parcel in) {
+            return new DeviceGroup(in);
+        }
+
+        public DeviceGroup[] newArray(int size) {
+            return new DeviceGroup[size];
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 108141c..f7276c3 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -87,6 +87,10 @@
     private final int mTDSFlagsMask;
     private final byte[] mWifiNANHash;
 
+    private final boolean mGroupBasedFiltering;
+
+    private static final int GROUP_DATA_LEN = 6;
+
     /** @hide */
     public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
 
@@ -96,7 +100,8 @@
             ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
             byte[] serviceData, byte[] serviceDataMask,
             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
-            int orgId, int TDSFlags, int TDSFlagsMask, byte[] wifiNANHash) {
+            int orgId, int TDSFlags, int TDSFlagsMask, byte[] wifiNANHash,
+            boolean groupBasedFiltering) {
         mDeviceName = name;
         mServiceUuid = uuid;
         mServiceUuidMask = uuidMask;
@@ -113,6 +118,7 @@
         mTDSFlags = TDSFlags;
         mTDSFlagsMask = TDSFlagsMask;
         mWifiNANHash = wifiNANHash;
+        mGroupBasedFiltering = groupBasedFiltering;
     }
 
     @Override
@@ -184,6 +190,7 @@
                 dest.writeByteArray(mWifiNANHash);
             }
         }
+        dest.writeBoolean(mGroupBasedFiltering);
     }
 
     /**
@@ -277,6 +284,8 @@
                 }
             }
 
+            boolean groupBasedFiltering = in.readBoolean();
+            builder.setGroupBasedFiltering(groupBasedFiltering);
             return builder.build();
         }
     };
@@ -387,6 +396,14 @@
     }
 
     /**
+     * @hide
+     * Returns true, if Group AD Type based filtering is enabled. Otherwise, false.
+     */
+    public boolean getGroupFilteringValue() {
+        return mGroupBasedFiltering;
+    }
+
+    /**
      * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
      * if it matches all the field filters.
      */
@@ -455,6 +472,13 @@
             }
         }
 
+        // Group AD Type filter match
+        if (mGroupBasedFiltering) {
+            if (scanRecord.getGroupIdentifierData().length != GROUP_DATA_LEN) {
+                return false;
+            }
+        }
+
         // All filters match.
         return true;
     }
@@ -552,7 +576,8 @@
                 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask)
                 + ", mOrganizationId=" + mOrgId + ", mTDSFlags=" + mTDSFlags
                 + ", mTDSFlagsMask=" + mTDSFlagsMask
-                + ", mWifiNANHash=" + Arrays.toString(mWifiNANHash) +"]";
+                + ", mWifiNANHash=" + Arrays.toString(mWifiNANHash) +"]"
+                + ", mGroupBasedFiltering=" + mGroupBasedFiltering;
     }
 
     @Override
@@ -565,7 +590,8 @@
                 Arrays.hashCode(mServiceDataMask),
                 mServiceUuid, mServiceUuidMask,
                 mServiceSolicitationUuid, mServiceSolicitationUuidMask,
-                mOrgId, mTDSFlags, mTDSFlagsMask, Arrays.hashCode(mWifiNANHash));
+                mOrgId, mTDSFlags, mTDSFlagsMask, Arrays.hashCode(mWifiNANHash),
+                mGroupBasedFiltering);
     }
 
     @Override
@@ -593,7 +619,8 @@
                 && mOrgId == other.mOrgId
                 && mTDSFlags == other.mTDSFlags
                 && mTDSFlagsMask == other.mTDSFlagsMask
-                && Objects.deepEquals(mWifiNANHash, other.mWifiNANHash);
+                && Objects.deepEquals(mWifiNANHash, other.mWifiNANHash)
+                && mGroupBasedFiltering == other.mGroupBasedFiltering;
     }
 
     /**
@@ -632,6 +659,8 @@
         private int mTDSFlagsMask = -1;
         private byte[] mWifiNANHash;
 
+        private boolean mGroupBasedFiltering;
+
         /**
          * Set filter on device name.
          */
@@ -837,6 +866,17 @@
             mWifiNANHash = wifiNANHash;
             return this;
         }
+
+        /**
+         * @hide
+         * Enable filter on Group AD Type.
+         */
+        public @NonNull Builder setGroupBasedFiltering(
+                boolean enable) {
+            mGroupBasedFiltering = enable;
+            return this;
+        }
+
         /**
          * Build {@link ScanFilter}.
          *
@@ -848,7 +888,8 @@
                     mServiceSolicitationUuidMask,
                     mServiceDataUuid, mServiceData, mServiceDataMask,
                     mManufacturerId, mManufacturerData, mManufacturerDataMask,
-                    mOrgId, mTDSFlags, mTDSFlagsMask, mWifiNANHash);
+                    mOrgId, mTDSFlags, mTDSFlagsMask, mWifiNANHash,
+                    mGroupBasedFiltering);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index abcb381..9509c53 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -57,6 +57,10 @@
     private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
     private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
     private static final int DATA_TYPE_TRANSPORT_DISCOVERY_DATA = 0x26;
+    /**
+     * @hide
+     */
+    public static int DATA_TYPE_GROUP_AD_TYPE = 0x00;
 
     // Flags of the advertising data.
     private final int mAdvertiseFlags;
@@ -82,6 +86,9 @@
     // Transport Discovery data.
     private final byte[] mTDSData;
 
+    // Group Identifier Data
+    private final byte[] mGroupIdentifierData;
+
     /**
      * Returns the advertising flags indicating the discoverable mode and capability of the device.
      * Returns -1 if the flag field is not set.
@@ -174,6 +181,14 @@
     }
 
     /**
+     * @hide
+     * Returns Group Identifier data
+     */
+    public byte[] getGroupIdentifierData() {
+        return mGroupIdentifierData;
+    }
+
+    /**
      * Returns raw bytes of scan record.
      */
     public byte[] getBytes() {
@@ -185,7 +200,7 @@
             SparseArray<byte[]> manufacturerData,
             Map<ParcelUuid, byte[]> serviceData,
             int advertiseFlags, int txPowerLevel,
-            String localName, byte[] tdsData, byte[] bytes) {
+            String localName, byte[] tdsData, byte[] groupIdentifierData, byte[] bytes) {
         mServiceSolicitationUuids = serviceSolicitationUuids;
         mServiceUuids = serviceUuids;
         mManufacturerSpecificData = manufacturerData;
@@ -194,6 +209,7 @@
         mAdvertiseFlags = advertiseFlags;
         mTxPowerLevel = txPowerLevel;
         mTDSData = tdsData;
+        mGroupIdentifierData = groupIdentifierData;
         mBytes = bytes;
     }
 
@@ -225,6 +241,7 @@
         Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
 
         byte[] tdsData = null;
+        byte[] groupIdentifierData = null;
 
         try {
             while (currentPos < scanRecord.length) {
@@ -306,8 +323,12 @@
                     case DATA_TYPE_TRANSPORT_DISCOVERY_DATA:
                         tdsData = extractBytes(scanRecord, currentPos, dataLength);
                         break;
+
                     default:
-                        // Just ignore, we don't handle such data type.
+                        if (fieldType == DATA_TYPE_GROUP_AD_TYPE) {
+                            Log.d(TAG, "Parsing Group Identifier data");
+                            groupIdentifierData = extractBytes(scanRecord, currentPos, dataLength);
+                        }
                         break;
                 }
                 currentPos += dataLength;
@@ -317,12 +338,14 @@
                 serviceUuids = null;
             }
             return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
-                    serviceData, advertiseFlag, txPowerLevel, localName, tdsData, scanRecord);
+                    serviceData, advertiseFlag, txPowerLevel, localName, tdsData,
+                    groupIdentifierData, scanRecord);
         } catch (Exception e) {
             Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
             // As the record is invalid, ignore all the parsed results for this packet
             // and return an empty record with raw scanRecord bytes in results
-            return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, null, scanRecord);
+            return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, null,
+                                  null, scanRecord);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index f0c12a3..b62f1c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -18,6 +18,8 @@
 
 import android.bluetooth.BluetoothCodecStatus;
 
+import java.util.UUID;
+
 /**
  * BluetoothCallback provides a callback interface for the settings
  * UI to receive events from {@link BluetoothEventManager}.
@@ -152,4 +154,27 @@
     default void onA2dpCodecConfigChanged(CachedBluetoothDevice cachedDevice,
             BluetoothCodecStatus codecStatus) {
     }
+
+    /**
+     * Called when new device group has been identified with the bonded remote device
+     *
+     * @param cachedDevice Bluetooth device with which device group has been found.
+     * @param groupId Identifier of the device group.
+     * @param setPrimaryServiceUuid Primary service with which this Device Group
+     *                              is associated.
+     */
+    default void onNewGroupFound(CachedBluetoothDevice cachedDevice, int groupId,
+            UUID setPrimaryServiceUuid) {
+    }
+
+    /**
+     * Called when Group Discovery status has been changed.
+     *
+     * @param groupId Identifier of the coordinated set.
+     * @param status Status of the group discovery procedure.
+     * @param reason Reason for the change in status of discovery.
+     */
+    default void onGroupDiscoveryStatusChanged (int groupId, int status, int reason) {
+    }
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 9b958e7..808acda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -42,6 +42,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.UUID;
 
 /**
  * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
@@ -249,6 +250,25 @@
         }
     }
 
+    protected void dispatchNewGroupFound(
+            CachedBluetoothDevice cachedDevice, int groupId, UUID setPrimaryServiceUuid) {
+        synchronized(mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onNewGroupFound(cachedDevice, groupId,
+                        setPrimaryServiceUuid);
+            }
+        }
+    }
+
+    protected void dispatchGroupDiscoveryStatusChanged(int groupId,
+            int status, int reason) {
+        synchronized(mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onGroupDiscoveryStatusChanged(groupId, status, reason);
+            }
+        }
+    }
+
     @VisibleForTesting
     void addHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/DeviceGroupClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/DeviceGroupClientProfile.java
new file mode 100644
index 0000000..5a3f33a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/DeviceGroupClientProfile.java
@@ -0,0 +1,377 @@
+/******************************************************************************
+ *  Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials provided
+ *        with the distribution.
+ *      * Neither the name of The Linux Foundation nor the names of its
+ *        contributors may be used to endorse or promote products derived
+ *        from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDeviceGroup;
+import android.bluetooth.BluetoothGroupCallback;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.DeviceGroup;
+import android.content.Context;
+import android.app.ActivityThread;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * DeviceGroupClientProfile handles group operations required in client Role.
+ */
+public class DeviceGroupClientProfile implements LocalBluetoothProfile {
+    private static final String TAG = "DeviceGroupClientProfile";
+
+    private BluetoothDeviceGroup mService;
+    private boolean mIsProfileReady;
+
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    static final String NAME = "DeviceGroup Client";
+    private static final String GROUP_APP = "com.android.settings";
+    private String mCallingPackage;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 3;
+
+    DeviceGroupClientProfile(Context context,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mCallingPackage = ActivityThread.currentOpPackageName();
+        BluetoothAdapter.getDefaultAdapter()
+                        .getProfileProxy(context, new GroupClientServiceListener(),
+                                         BluetoothProfile.GROUP_CLIENT);
+    }
+
+    // These callbacks run on the main thread.
+    private final class GroupClientServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothDeviceGroup) proxy;
+            mIsProfileReady = true;
+            Log.d(TAG, "onServiceConnected: mCallingPackage = " + mCallingPackage);
+            // register Group Client App
+            if (GROUP_APP.equals(mCallingPackage)) {
+                mService.registerGroupClientApp(mGroupCallback,
+                        new Handler(Looper.getMainLooper()));
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mIsProfileReady=false;
+        }
+    }
+
+    private final BluetoothGroupCallback mGroupCallback = new BluetoothGroupCallback() {
+
+        @Override
+        public void onNewGroupFound (int groupId,  BluetoothDevice device, UUID uuid) {
+            Log.d(TAG, "onNewGroupFound()");
+
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                cachedDevice = mDeviceManager.addDevice(device);
+            }
+
+            mProfileManager.mEventManager.dispatchNewGroupFound(
+                    cachedDevice, groupId, uuid);
+            Log.d(TAG, "Start Group Discovery for Audio capable device");
+            //if (device.isAdvAudioDevice())
+                mService.startGroupDiscovery(groupId);
+        }
+
+        @Override
+        public void onGroupDiscoveryStatusChanged (int groupId,
+                int status, int reason) {
+            Log.d(TAG, "onGroupDiscoveryStatusChanged()");
+
+            mProfileManager.mEventManager.dispatchGroupDiscoveryStatusChanged(
+                        groupId, status, reason);
+        }
+
+    };
+
+    public boolean connectGroup (int groupId) {
+        Log.d(TAG, "connectGroup(): groupId = " + groupId);
+        boolean isTriggered = false;
+
+        if(mService == null || mIsProfileReady == false) {
+            Log.e(TAG, "connectGroup:  mService = " + mService +
+                " mIsProfileReady = " + mIsProfileReady);
+            return false;
+        }
+
+        DeviceGroup mGroup = mService.getGroup(groupId);
+
+        if (mGroup == null || mGroup.getDeviceGroupMembers().size() == 0) {
+            Log.e(TAG, "Requested device group not found");
+            return false;
+        }
+
+        for (BluetoothDevice device: mGroup.getDeviceGroupMembers()) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "CachedBluetoothDevice not found for device: " + device);
+                continue;
+            }
+
+            if (!cachedDevice.isConnected()) {
+                cachedDevice.connect(true);
+                isTriggered = true;
+            }
+        }
+        return isTriggered;
+    }
+
+    public boolean disconnectGroup (int groupId) {
+        Log.d(TAG, "disconnectGroup(): groupId = " + groupId);
+        boolean isTriggered = false;
+
+        if(mService == null || mIsProfileReady == false) {
+            Log.e(TAG, "connectGroup:  mService = " + mService +
+                " mIsProfileReady = " + mIsProfileReady);
+            return false;
+        }
+
+        DeviceGroup mGroup = mService.getGroup(groupId);
+
+        if (mGroup == null || mGroup.getDeviceGroupMembers().size() == 0) {
+            Log.e(TAG, "Requested device group is not found");
+            return false;
+        }
+
+        for (BluetoothDevice device: mGroup.getDeviceGroupMembers()) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "CachedBluetoothDevice not found for device: " + device);
+                continue;
+            }
+
+            if (cachedDevice.isConnected()) {
+                cachedDevice.disconnect();
+                isTriggered = true;
+            }
+        }
+
+        return isTriggered;
+    }
+
+    public boolean forgetGroup(int groupId) {
+        Log.d(TAG, "forgetGroup(): groupId = " + groupId);
+
+        if(mService == null || mIsProfileReady == false) {
+            Log.e(TAG, "forgetGroup:  mService = " + mService +
+                " mIsProfileReady = " + mIsProfileReady);
+            return false;
+        }
+
+        DeviceGroup mGroup = mService.getGroup(groupId);
+        if (mGroup == null || mGroup.getDeviceGroupMembers().size() == 0) {
+            Log.e(TAG, "Requested device group is not found");
+            return false;
+        }
+
+        for (BluetoothDevice device: mGroup.getDeviceGroupMembers()) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice == null) {
+                Log.w(TAG, "CachedBluetoothDevice not found for device: " + device);
+                continue;
+            }
+            cachedDevice.unpair();
+        }
+
+        return true;
+    }
+
+    public boolean startGroupDiscovery (int groupId) {
+       Log.d(TAG, "startGroupDiscovery: groupId = " + groupId);
+
+       if(mService == null || mIsProfileReady == false) {
+           Log.e(TAG, "startGroupDiscovery:  mService = " + mService +
+               " mIsProfileReady = " + mIsProfileReady);
+           return false;
+       }
+
+        return mService.startGroupDiscovery(groupId);
+    }
+
+    public boolean stopGroupDiscovery (int groupId) {
+       Log.d(TAG, "stopGroupDiscovery: groupId = " + groupId);
+
+       if(mService == null || mIsProfileReady == false) {
+           Log.e(TAG, "stopGroupDiscovery:  mService = " + mService +
+               " mIsProfileReady = " + mIsProfileReady);
+           return false;
+       }
+
+        return mService.stopGroupDiscovery(groupId);
+    }
+
+    public DeviceGroup getGroup (int groupId) {
+        Log.d(TAG, "getGroup: groupId = " + groupId);
+
+        if(mService == null || mIsProfileReady == false) {
+            Log.e(TAG, "getGroup:  mService = " + mService +
+                " mIsProfileReady = " + mIsProfileReady);
+            return null;
+        }
+
+        return mService.getGroup(groupId, true);
+    }
+
+    public List<DeviceGroup> getDiscoveredGroups () {
+       Log.d(TAG, "getDiscoveredGroups");
+
+       if(mService == null || mIsProfileReady == false) {
+           Log.e(TAG, "getDiscoveredGroups:  mService = " + mService +
+               " mIsProfileReady = " + mIsProfileReady);
+           return null;
+       }
+
+       return mService.getDiscoveredGroups(true);
+    }
+
+    public boolean isGroupDiscoveryInProgress (int groupId) {
+       Log.d(TAG, "isGroupDiscoveryInProgress: groupId = " + groupId);
+
+       if (mService == null) {
+            Log.e(TAG, "Not connected to Profile Service. Return.");
+            return false;
+        }
+
+       return mService.isGroupDiscoveryInProgress(groupId);
+    }
+
+    public int getRemoteDeviceGroupId (BluetoothDevice device) {
+       Log.d(TAG, "getRemoteDeviceGroupId: device = " + device);
+
+       if(mService == null || mIsProfileReady == false) {
+           Log.e(TAG, "getRemoteDeviceGroupId:  mService = " + mService +
+               " mIsProfileReady = " + mIsProfileReady);
+           return BluetoothDeviceGroup.INVALID_GROUP_ID;
+       }
+
+       return mService.getRemoteDeviceGroupId(device, null, true);
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    @Override
+    public int getProfileId() {
+        return BluetoothProfile.GROUP_CLIENT;
+    }
+
+    public boolean accessProfileEnabled() {
+        return false;
+    }
+
+    public boolean isAutoConnectable() {
+        return false;
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {}
+
+    public int getPreferred(BluetoothDevice device) { return BluetoothProfile.PRIORITY_OFF;}
+
+    public boolean isPreferred(BluetoothDevice device) {return false;}
+
+    public boolean connect(BluetoothDevice device) { return false;}
+
+    public boolean disconnect(BluetoothDevice device) {return false;}
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
+        return 0;
+    }
+
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+
+        return isEnabled;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        return 0;//R.string.bluetooth_profile_group_client;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return 0;
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return 0;
+    }
+
+    protected void finalize() {
+        Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter()
+                                .closeProfileProxy(BluetoothProfile.GROUP_CLIENT,
+                                                   mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up BluetoothDeviceGroup proxy Object", t);
+            }
+        }
+    }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 81a3fec..cd6e32b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDeviceGroup;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
@@ -87,10 +88,11 @@
 
     private final Context mContext;
     private final CachedBluetoothDeviceManager mDeviceManager;
-    private final BluetoothEventManager mEventManager;
+    protected final BluetoothEventManager mEventManager;
 
     private A2dpProfile mA2dpProfile;
     private A2dpSinkProfile mA2dpSinkProfile;
+    private DeviceGroupClientProfile mGroupClientProfile;
     private HeadsetProfile mHeadsetProfile;
     private HfpClientProfile mHfpClientProfile;
     private MapProfile mMapProfile;
@@ -229,6 +231,12 @@
             addProfile(mDunProfile, DunServerProfile.NAME,
                     BluetoothDun.ACTION_CONNECTION_STATE_CHANGED);
         }
+        if (mGroupClientProfile == null && supportedList.contains(BluetoothProfile.GROUP_CLIENT)) {
+            if (DEBUG) Log.d(TAG, "Adding local GROUP CLIENT profile");
+            mGroupClientProfile = new DeviceGroupClientProfile(mContext, mDeviceManager, this);
+            addProfile(mGroupClientProfile, mGroupClientProfile.NAME,
+                    BluetoothDeviceGroup.ACTION_CONNECTION_STATE_CHANGED);
+        }
         mEventManager.registerProfileIntentReceiver();
     }
 
@@ -465,6 +473,9 @@
         return mHidDeviceProfile;
     }
 
+    public DeviceGroupClientProfile getDeviceGroupClientProfile() {
+        return mGroupClientProfile;
+    }
     /**
      * Fill in a list of LocalBluetoothProfile objects that are supported by
      * the local device and the remote device.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
old mode 100644
new mode 100755
index faeda81..2d0e268
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -434,7 +434,7 @@
             }
         }
         for (int i = 0; i < changedSubscriptions.size(); i++) {
-            SimData data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
+            SimData data = mSimDatas.get(changedSubscriptions.get(i).getSimSlotIndex());
             for (int j = 0; j < mCallbacks.size(); j++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
                 if (cb != null) {
@@ -2386,7 +2386,13 @@
                 // Even though the subscription is not valid anymore, we need to notify that the
                 // SIM card was removed so we can update the UI.
                 becameAbsent = true;
-                mSimDatas.clear();
+                for (SimData data : mSimDatas.values()) {
+                    // Set the SIM state of all SimData associated with that slot to ABSENT se we
+                    // do not move back into PIN/PUK locked and not detect the change below.
+                    if (data.slotId == slotId) {
+                        data.simState = TelephonyManager.SIM_STATE_ABSENT;
+                    }
+                }
             } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
                 updateTelephonyCapable(true);
             } else {
@@ -2394,11 +2400,11 @@
             }
         }
 
-        SimData data = mSimDatas.get(subId);
+        SimData data = mSimDatas.get(slotId);
         final boolean changed;
         if (data == null) {
             data = new SimData(state, slotId, subId);
-            mSimDatas.put(subId, data);
+            mSimDatas.put(slotId, data);
             changed = true; // no data yet; force update
         } else {
             changed = (data.simState != state || data.subId != subId || data.slotId != slotId);
@@ -2741,18 +2747,20 @@
     }
 
     public int getSimState(int subId) {
-        if (mSimDatas.containsKey(subId)) {
-            return mSimDatas.get(subId).simState;
+        int slotId = SubscriptionManager.getSlotIndex(subId);
+        if (mSimDatas.containsKey(slotId)) {
+            return mSimDatas.get(slotId).simState;
         } else {
             return TelephonyManager.SIM_STATE_UNKNOWN;
         }
     }
 
     private int getSlotId(int subId) {
-        if (!mSimDatas.containsKey(subId)) {
-            refreshSimState(subId, SubscriptionManager.getSlotIndex(subId));
+        int slotId = SubscriptionManager.getSlotIndex(subId);
+        if (!mSimDatas.containsKey(slotId)) {
+            refreshSimState(subId, slotId);
         }
-        return mSimDatas.get(subId).slotId;
+        return mSimDatas.get(slotId).slotId;
     }
 
     private final TaskStackChangeListener
@@ -2811,11 +2819,11 @@
                 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         int state = (tele != null) ?
                 tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
-        SimData data = mSimDatas.get(subId);
+        SimData data = mSimDatas.get(slotId);
         final boolean changed;
         if (data == null) {
             data = new SimData(state, slotId, subId);
-            mSimDatas.put(subId, data);
+            mSimDatas.put(slotId, data);
             changed = true; // no data yet; force update
         } else {
             changed = (data.simState != state) || (data.slotId != slotId);
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index dd001ba..dd74909 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -92,6 +92,8 @@
 
     void allowAutojoinGlobal(boolean choice);
 
+    void allowConnectOnPartialScanResults(boolean enable);
+
     void allowAutojoin(int netId, boolean choice);
 
     void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 15415f0..3346e10 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1052,6 +1052,15 @@
     public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
 
     /**
+     * An access point partial scan has completed, and results are available.
+     * Call {@link #getScanResults()} to obtain the results.
+     * The broadcast intent may contain an extra field with the key {@link #EXTRA_RESULTS_UPDATED}
+     * and a {@code boolean} value indicating if the scan was successful.
+     * @hide
+     */
+    public static final String PARTIAL_SCAN_RESULTS_AVAILABLE_ACTION = "com.qualcomm.qti.net.wifi.PARTIAL_SCAN_RESULTS";
+
+    /**
      * Lookup key for a {@code boolean} extra in intent {@link #SCAN_RESULTS_AVAILABLE_ACTION}
      * representing if the scan was successful or not.
      * Scans may fail for multiple reasons, these may include:
@@ -4578,6 +4587,19 @@
         }
     }
 
+    /**
+     * Enable/disable quick connect on partial scan results.
+     *
+     * @param  enable true to not allow quick connect, false to allow quick connect
+     * @hide
+     */
+    public void allowConnectOnPartialScanResults(boolean enable) {
+        try {
+            mService.allowConnectOnPartialScanResults(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Sets the user choice for allowing auto-join to a network.
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 94771ac..4d61351 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -154,6 +154,16 @@
     /** An outstanding request with the same listener hasn't finished yet. */
     public static final int REASON_DUPLICATE_REQEUST = -5;
 
+    /** Partial scan results msg arg
+     * @hide
+     */
+     public static final int ON_PARTIAL_SCAN_RESULTS = 1;
+
+    /** Complete scan results msg arg
+     * @hide
+     */
+     public static final int ON_COMPLETE_SCAN_RESULTS = 0;
+
     /** @hide */
     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
 
@@ -865,6 +875,11 @@
          */
         public void onResults(ScanData[] results);
         /**
+         * reports partial results retrieved from single shot scans
+         * @hide
+         */
+        default void onPartialScanResults(ScanData[] results) {}
+        /**
          * reports full scan result for each access point found in scan
          */
         public void onFullResult(ScanResult fullScanResult);
@@ -1648,7 +1663,11 @@
                     ScanListener scanListener = (ScanListener) listener;
                     ParcelableScanData parcelableScanData = (ParcelableScanData) msg.obj;
                     Binder.clearCallingIdentity();
-                    executor.execute(() -> scanListener.onResults(parcelableScanData.getResults()));
+                    if (msg.arg1 == ON_PARTIAL_SCAN_RESULTS) {
+                        executor.execute(() -> scanListener.onPartialScanResults(parcelableScanData.getResults()));
+                    } else {
+                        executor.execute(() -> scanListener.onResults(parcelableScanData.getResults()));
+                    }
                 } break;
                 case CMD_FULL_SCAN_RESULT: {
                     ScanResult result = (ScanResult) msg.obj;