/****************************************************************************** * 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.RequiresPermission; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.bluetooth.annotations.RequiresBluetoothScanPermission; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; 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. * *
This class provides functionalities to enable communication with remote * devices which are grouped together to achieve common use cases in * synchronized manner. *
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. *
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 AttributionSource mAttributionSource;
private final BluetoothProfileConnector 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.
*
* 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.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
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, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
}
return true;
}
/**
* Stops ongoing group discovery for Group identified by groupId.
*
* {@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.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
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, mAttributionSource);
} 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.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List 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.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
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, mAttributionSource);
} 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.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
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, mAttributionSource);
} 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);
}
}
}