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;