diff options
Diffstat (limited to 'framework/java')
23 files changed, 1927 insertions, 76 deletions
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index 0fdadf85eb..67515bef5c 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -86,7 +86,9 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; - +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; /** * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter} * lets you perform fundamental Bluetooth tasks, such as initiate @@ -1016,6 +1018,7 @@ public final class BluetoothAdapter { */ @RequiresNoPermission public BluetoothDevice getRemoteDevice(String address) { + android.util.SeempLog.record(62); final BluetoothDevice res = new BluetoothDevice(address); res.setAttributionSource(mAttributionSource); return res; @@ -1055,6 +1058,7 @@ public final class BluetoothAdapter { */ @RequiresNoPermission public BluetoothDevice getRemoteDevice(byte[] address) { + android.util.SeempLog.record(62); if (address == null || address.length != 6) { throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); } @@ -1337,6 +1341,7 @@ public final class BluetoothAdapter { @RequiresLegacyBluetoothPermission @RequiresNoPermission public @AdapterState int getState() { + android.util.SeempLog.record(63); int state = getStateInternal(); // Consider all internal states as OFF @@ -1434,6 +1439,7 @@ public final class BluetoothAdapter { @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enable() { + android.util.SeempLog.record(56); if (isEnabled()) { if (DBG) { Log.d(TAG, "enable(): BT already enabled!"); @@ -1486,6 +1492,7 @@ public final class BluetoothAdapter { @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable() { + android.util.SeempLog.record(57); try { return mManagerService.disable(mAttributionSource, true); } catch (RemoteException e) { @@ -1509,6 +1516,7 @@ public final class BluetoothAdapter { android.Manifest.permission.BLUETOOTH_PRIVILEGED, }) public boolean disable(boolean persist) { + android.util.SeempLog.record(57); try { return mManagerService.disable(mAttributionSource, persist); @@ -2128,6 +2136,7 @@ public final class BluetoothAdapter { @RequiresBluetoothLocationPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery() { + android.util.SeempLog.record(58); if (getState() != STATE_ON) { return false; } @@ -2323,7 +2332,21 @@ public final class BluetoothAdapter { return false; } - + /** @hide */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public boolean isBroadcastActive() { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.isBroadcastActive(mAttributionSource); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + } /** * Get the active devices for the BluetoothProfile specified * @@ -2750,6 +2773,13 @@ public final class BluetoothAdapter { } /** + * @hide + */ + public void btCmdGetFunctionCallmap(boolean isdump) { + Log.d(TAG, "btCmdGetFunctionCallmap: " + isdump); + } + + /** * Return true if Hearing Aid Profile is supported. * * @return true if phone supports Hearing Aid Profile @@ -2911,6 +2941,7 @@ public final class BluetoothAdapter { @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public Set<BluetoothDevice> getBondedDevices() { + android.util.SeempLog.record(61); if (getState() != STATE_ON) { return toDeviceSet(Arrays.asList()); } @@ -3094,6 +3125,7 @@ public final class BluetoothAdapter { @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SuppressLint("AndroidFrameworkRequiresPermission") public @ConnectionState int getProfileConnectionState(int profile) { + android.util.SeempLog.record(64); if (getState() != STATE_ON) { return STATE_DISCONNECTED; } @@ -3378,6 +3410,7 @@ public final class BluetoothAdapter { @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) throws IOException { + android.util.SeempLog.record(59); return createNewRfcommSocketAndRecord(name, uuid, false, false); } @@ -3575,6 +3608,92 @@ public final class BluetoothAdapter { return null; } + private void closeBCProfile(BluetoothProfile proxy) { + Class<?> bshClass = null; + Method bshClose = null; + try { + bshClass = Class.forName("android.bluetooth.BluetoothSyncHelper"); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "no BSH: exists"); + bshClass = null; + } + if (bshClass != null) { + Log.d(TAG, "Able to get BSH class handle"); + try { + bshClose = bshClass.getDeclaredMethod("close", null); + } catch (NoSuchMethodException e) { + Log.e(TAG, "no BSH:isSupported method exists"); + } + if (bshClose != null) { + try { + bshClose.invoke(proxy, null); + } catch(IllegalAccessException e) { + Log.e(TAG, "bshClose IllegalAccessException"); + } catch (InvocationTargetException e) { + Log.e(TAG, "bshClose InvocationTargetException"); + } + } + } + Log.d(TAG, "CloseBCProfile returns"); + } + + private boolean getBCProfile(Context context, BluetoothProfile.ServiceListener sl) { + boolean ret = true; + boolean isProfileSupported = false; + Class<?> bshClass = null; + Method bshSupported = null; + Constructor bshCons = null; + Object bshObj = null; + try { + bshClass = Class.forName("android.bluetooth.BluetoothSyncHelper"); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "no BSH: exists"); + bshClass = null; + } + if (bshClass != null) { + Log.d(TAG, "Able to get BSH class handle"); + try { + bshSupported = bshClass.getDeclaredMethod("isSupported", null); + } catch (NoSuchMethodException e) { + Log.e(TAG, "no BSH:isSupported method exists: gdm"); + } + try { + bshCons = + bshClass.getDeclaredConstructor( + new Class[]{Context.class, + BluetoothProfile.ServiceListener.class}); + } catch (NoSuchMethodException ex) { + Log.e(TAG, "bshCons: NoSuchMethodException: gdm" + ex); + } + } + if (bshClass != null && bshSupported != null && bshCons != null) { + try { + isProfileSupported = (boolean)bshSupported.invoke(null, null); + } catch(IllegalAccessException e) { + Log.e(TAG, "BSH:isSupported IllegalAccessException"); + } catch (InvocationTargetException e) { + Log.e(TAG, "BSH:isSupported InvocationTargetException"); + } + if (isProfileSupported) { + try { + bshObj = bshCons.newInstance( + context, sl); + } catch (InstantiationException ex) { + Log.e(TAG, "bshCons InstantiationException:" + ex); + } catch (IllegalAccessException ex) { + Log.e(TAG, "bshCons InstantiationException:" + ex); + } catch (InvocationTargetException ex) { + Log.e(TAG, "bshCons InvocationTargetException:" + ex); + } + } + } + if (bshObj == null) { + ret = false; + } + Log.d(TAG, "getBCService returns" + ret); + return ret; + } + /** * Get the profile proxy object associated with the profile. * @@ -3622,6 +3741,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.PBAP) { BluetoothPbap pbap = new BluetoothPbap(context, listener, this); return true; + } else if (profile == BluetoothProfile.DUN) { + BluetoothDun dun = new BluetoothDun(context, listener); + return true; } else if (profile == BluetoothProfile.HEALTH) { Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); return false; @@ -3647,12 +3769,22 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.HAP_CLIENT) { BluetoothHapClient HapClient = new BluetoothHapClient(context, listener); return true; + } else if (profile == BluetoothProfile.BROADCAST) { + return getBroadcastProfile(context, listener); + } else if (profile == BluetoothProfile.BC_PROFILE) { + return getBCProfile(context, listener); } else if (profile == BluetoothProfile.HEARING_AID) { if (isHearingAidProfileSupported()) { BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener, this); return true; } return false; + } else if (profile == BluetoothProfile.GROUP_CLIENT) { + BluetoothDeviceGroup groupClient = new BluetoothDeviceGroup(context, listener); + return true; + } else if (profile == BluetoothProfile.VCP) { + BluetoothVcp vcp = new BluetoothVcp(context, listener); + return true; } else if (profile == BluetoothProfile.LE_AUDIO) { BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this); return true; @@ -3726,6 +3858,10 @@ public final class BluetoothAdapter { BluetoothPbap pbap = (BluetoothPbap) proxy; pbap.close(); break; + case BluetoothProfile.DUN: + BluetoothDun dun = (BluetoothDun)proxy; + dun.close(); + break; case BluetoothProfile.GATT: BluetoothGatt gatt = (BluetoothGatt) proxy; gatt.close(); @@ -3762,6 +3898,12 @@ public final class BluetoothAdapter { BluetoothHapClient HapClient = (BluetoothHapClient) proxy; HapClient.close(); break; + case BluetoothProfile.BROADCAST: + closeBroadcastProfile(proxy); + break; + case BluetoothProfile.BC_PROFILE: + closeBCProfile(proxy); + break; case BluetoothProfile.HEARING_AID: BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy; hearingAid.close(); @@ -3774,6 +3916,14 @@ public final class BluetoothAdapter { BluetoothLeBroadcast leAudioBroadcast = (BluetoothLeBroadcast) proxy; leAudioBroadcast.close(); break; + case BluetoothProfile.GROUP_CLIENT: + BluetoothDeviceGroup groupClient = (BluetoothDeviceGroup) proxy; + groupClient.close(); + break; + case BluetoothProfile.VCP: + BluetoothVcp vcp = (BluetoothVcp) proxy; + vcp.close(); + break; case BluetoothProfile.VOLUME_CONTROL: BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy; vcs.close(); @@ -3795,6 +3945,63 @@ public final class BluetoothAdapter { } } + private boolean getBroadcastProfile(Context context, + BluetoothProfile.ServiceListener listener) { + boolean ret = true; + Class<?> broadcastClass = null; + Constructor bcastConstructor = null; + Object broadcastObj = null; + try { + broadcastClass = Class.forName("android.bluetooth.BluetoothBroadcast"); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "no BluetoothBroadcast: exists"); + } + if (broadcastClass != null) { + try { + bcastConstructor = + broadcastClass.getDeclaredConstructor(new Class[]{Context.class, + BluetoothProfile.ServiceListener.class}); + } catch (NoSuchMethodException ex) { + Log.e(TAG, "bcastConstructor: NoSuchMethodException: gdm" + ex); + } + } + if (bcastConstructor != null) { + try { + broadcastObj = bcastConstructor.newInstance(context, listener); + } catch (InstantiationException | IllegalAccessException | + InvocationTargetException ex) { + ex.printStackTrace(); + } + } + if (broadcastObj == null) { + return false; + } + return true; + } + private void closeBroadcastProfile(BluetoothProfile proxy) { + Class<?> broadcastClass = null; + Method broadcastClose = null; + try { + broadcastClass = Class.forName("android.bluetooth.BluetootBroadcast"); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "no BluetoothBroadcast: exists"); + } + if (broadcastClass != null) { + try { + broadcastClose = broadcastClass.getDeclaredMethod("close", null); + } catch (NoSuchMethodException e) { + Log.e(TAG, "no Broadcast:close method exists"); + } + if (broadcastClose != null) { + try { + broadcastClose.invoke(proxy, null); + } catch(IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + } + } + } + } + private static final IBluetoothManagerCallback sManagerCallback = new IBluetoothManagerCallback.Stub() { public void onBluetoothServiceUp(IBluetooth bluetoothService) { @@ -3890,8 +4097,10 @@ public final class BluetoothAdapter { try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - mService.registerBluetoothConnectionCallback(mConnectionCallback, + if (mService != null) { + mService.registerBluetoothConnectionCallback(mConnectionCallback, mAttributionSource, recv); + } recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } catch (RemoteException | TimeoutException e) { Log.e(TAG, "onBluetoothServiceUp: Failed to register bluetooth" @@ -3918,6 +4127,7 @@ public final class BluetoothAdapter { } finally { l.unlock(); } + Log.d(TAG, "onBluetoothServiceDown: Finished sending callbacks to registered clients"); } public void onBrEdrDown() { @@ -4150,6 +4360,22 @@ public final class BluetoothAdapter { } } + /** + * @hide + */ + public void unregisterAdapter() { + try { + //mServiceLock.writeLock().lock(); + if (mManagerService != null){ + mManagerService.unregisterAdapter(mManagerCallback); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + //mServiceLock.writeLock().unlock(); + } + } + private Set<BluetoothDevice> toDeviceSet(List<BluetoothDevice> devices) { Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(devices); return Collections.unmodifiableSet(deviceSet); diff --git a/framework/java/android/bluetooth/BluetoothClass.java b/framework/java/android/bluetooth/BluetoothClass.java index 3561eeff90..ea747e1416 100755 --- a/framework/java/android/bluetooth/BluetoothClass.java +++ b/framework/java/android/bluetooth/BluetoothClass.java @@ -121,6 +121,11 @@ public final class BluetoothClass implements Parcelable { public static final int LIMITED_DISCOVERABILITY = 0x002000; /** Represent devices LE audio service */ public static final int LE_AUDIO = 0x004000; + /** + * @hide + */ + public static final int GROUP = 0x008000; + public static final int POSITIONING = 0x010000; public static final int NETWORKING = 0x020000; public static final int RENDER = 0x040000; diff --git a/framework/java/android/bluetooth/BluetoothCodecConfig.java b/framework/java/android/bluetooth/BluetoothCodecConfig.java index 9fc9fb3411..d1fb561107 100644 --- a/framework/java/android/bluetooth/BluetoothCodecConfig.java +++ b/framework/java/android/bluetooth/BluetoothCodecConfig.java @@ -41,7 +41,11 @@ public final class BluetoothCodecConfig implements Parcelable { @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {SOURCE_CODEC_TYPE_SBC, SOURCE_CODEC_TYPE_AAC, SOURCE_CODEC_TYPE_APTX, SOURCE_CODEC_TYPE_APTX_HD, SOURCE_CODEC_TYPE_LDAC, SOURCE_CODEC_TYPE_LC3, - SOURCE_CODEC_TYPE_INVALID}) + SOURCE_CODEC_TYPE_INVALID, + SOURCE_CODEC_TYPE_APTX_ADAPTIVE, + SOURCE_CODEC_TYPE_APTX_TWSP, + SOURCE_QVA_CODEC_TYPE_MAX + }) @Retention(RetentionPolicy.SOURCE) public @interface SourceCodecType {} @@ -85,7 +89,18 @@ public final class BluetoothCodecConfig implements Parcelable { /** * Represents the count of valid source codec types. */ - private static final int SOURCE_CODEC_TYPE_MAX = 6; + public static final int SOURCE_CODEC_TYPE_MAX = 6; + + public static final int SOURCE_CODEC_TYPE_APTX_ADAPTIVE = SOURCE_CODEC_TYPE_MAX; + + public static final int SOURCE_CODEC_TYPE_APTX_TWSP = SOURCE_CODEC_TYPE_MAX + 1; + + public static final int SOURCE_QVA_CODEC_TYPE_MAX = SOURCE_CODEC_TYPE_MAX + 2; + + /* CELT is not an A2DP Codec and only used to fetch encoder + ** format for BA usecase, moving out of a2dp codec value list + */ + public static final int SOURCE_CODEC_TYPE_CELT = 8; /** @hide */ @IntDef(prefix = "CODEC_PRIORITY_", value = { @@ -163,6 +178,14 @@ public final class BluetoothCodecConfig implements Parcelable { */ public static final int SAMPLE_RATE_192000 = 0x1 << 5; + public static final int SAMPLE_RATE_16000 = 0x1 << 6; + + public static final int SAMPLE_RATE_24000 = 0x1 << 7; + + public static final int SAMPLE_RATE_32000 = 0x1 << 8; + + public static final int SAMPLE_RATE_8000 = 0x1 << 9; + /** @hide */ @IntDef(prefix = "BITS_PER_SAMPLE_", value = { BITS_PER_SAMPLE_NONE, @@ -218,6 +241,7 @@ public final class BluetoothCodecConfig implements Parcelable { * Codec channel mode STEREO. */ public static final int CHANNEL_MODE_STEREO = 0x1 << 1; + public static final int CHANNEL_MODE_JOINT_STEREO = 0x1 << 2; private final @SourceCodecType int mCodecType; private @CodecPriority int mCodecPriority; @@ -463,6 +487,10 @@ public final class BluetoothCodecConfig implements Parcelable { return "LDAC"; case SOURCE_CODEC_TYPE_LC3: return "LC3"; + case SOURCE_CODEC_TYPE_APTX_ADAPTIVE: + return "aptX Adaptive"; + case SOURCE_CODEC_TYPE_APTX_TWSP: + return "aptX TWS+"; case SOURCE_CODEC_TYPE_INVALID: return "INVALID CODEC"; default: @@ -712,9 +740,13 @@ public final class BluetoothCodecConfig implements Parcelable { case SOURCE_CODEC_TYPE_AAC: case SOURCE_CODEC_TYPE_LDAC: case SOURCE_CODEC_TYPE_LC3: - if (mCodecSpecific1 != other.mCodecSpecific1) { - return false; - } + if (mCodecSpecific1 != other.mCodecSpecific1) { + return false; + } + case SOURCE_CODEC_TYPE_APTX_ADAPTIVE: + if (other.mCodecSpecific4 > 0) { + return false; + } default: return true; } diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java index e20165f522..5ab621f627 100644 --- a/framework/java/android/bluetooth/BluetoothDevice.java +++ b/framework/java/android/bluetooth/BluetoothDevice.java @@ -1,4 +1,39 @@ /* + * Copyright (C) 2017, The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) 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. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. + */ + +/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -226,6 +261,18 @@ public final class BluetoothDevice implements Parcelable, Attributable { public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED"; + /** + * Broadcast Action: Broadcast details of IOT device when an IOT + * related issue is observed. + * <p>Always contains the extra fields {@link #EXTRA_NAME}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide + **/ + + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_REMOTE_ISSUE_OCCURRED = + "org.codeaurora.intent.bluetooth.action.REMOTE_ISSUE_OCCURRED"; + /** * Broadcast Action: Indicates the battery level of a remote device has * been retrieved for the first time, or changed since the last retrieval @@ -294,6 +341,17 @@ public final class BluetoothDevice implements Parcelable, Attributable { public static final int BATTERY_LEVEL_BLUETOOTH_OFF = -100; /** + * Broadcast Action: Indicates the remote devices are TWS plus earbuds pair. + * <p>Always contains the extra fields {@link #EXTRA_TWS_PLUS_DEVICE1}, + * {@link #EXTRA_TWS_PLUS_DEVICE2}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TWS_PLUS_DEVICE_PAIR = + "android.bluetooth.device.action.TWS_PLUS_DEVICE_PAIR"; + + /** * Used as a Parcelable {@link BluetoothDevice} extra field in every intent * broadcast by this class. It contains the {@link BluetoothDevice} that * the intent applies to. @@ -307,6 +365,77 @@ public final class BluetoothDevice implements Parcelable, Attributable { public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME"; /** + * Used as a Parcelable {@link BluetoothQualityReport} extra field in + * {@link #ACTION_REMOTE_ISSUE_OCCURRED} intent. It contains the {@link BluetoothQualityReport}. + * @hide + */ + public static final String EXTRA_BQR = "android.bluetooth.qti.extra.EXTRA_BQR"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} + * intents. It contains the type of IOT issue that occurred. + * @hide + */ + public static final String EXTRA_ISSUE_TYPE = "android.bluetooth.qti.extra.ERROR_TYPE"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the details of details of the issue. + * @hide + */ + public static final String EXTRA_ERROR_CODE = "android.bluetooth.qti.extra.ERROR_CODE"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the SoC event mask when issue occurred. + * @hide + */ + public static final String EXTRA_ERROR_EVENT_MASK = "android.bluetooth.qti.extra.ERROR_EVENT_MASK"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the LMP Version of IOT device. + * @hide + */ + public static final String EXTRA_LMP_VERSION = "android.bluetooth.qti.extra.EXTRA_LMP_VERSION"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the LMP Sub Version of IOT device. + * @hide + */ + public static final String EXTRA_LMP_SUBVER = "android.bluetooth.qti.extra.EXTRA_LMP_SUBVER"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the Manufacturer ID of IOT device. + * @hide + */ + public static final String EXTRA_MANUFACTURER = "android.bluetooth.qti.extra.EXTRA_MANUFACTURER"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the Power level. + * @hide + */ + public static final String EXTRA_POWER_LEVEL = "android.bluetooth.qti.extra.EXTRA_POWER_LEVEL"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the Link Quality of the connection. + * @hide + */ + public static final String EXTRA_LINK_QUALITY = "android.bluetooth.qti.extra.EXTRA_LINK_QUALITY"; + + /** + * Used as a String extra field in {@link #ACTION_REMOTE_ISSUE_OCCURRED} intents. + * It contains the coutnt of glitches occured since last broadcast. + * @hide + */ + public static final String EXTRA_GLITCH_COUNT = "android.bluetooth.qti.extra.EXTRA_GLITCH_COUNT"; + + + /** * Used as an optional short extra field in {@link #ACTION_FOUND} intents. * Contains the RSSI value of the remote device as reported by the * Bluetooth hardware. @@ -359,6 +488,22 @@ public final class BluetoothDevice implements Parcelable, Attributable { "android.bluetooth.device.extra.LOW_LATENCY_BUFFER_SIZE"; /** + * Used as a String extra field in {@link #ACTION_TWS+_DEVICE_PAIR} + * intents. It contains the first TWS+ earbud address of pair. + * @hide + */ + public static final String EXTRA_TWS_PLUS_DEVICE1 = + "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE1"; + + /** + * Used as a String extra field in {@link #ACTION_TWS+_DEVICE_PAIR} + * intents. It contains the second TWS+ earbud address of pair. + * @hide + */ + public static final String EXTRA_TWS_PLUS_DEVICE2 = + "android.bluetooth.device.extra.EXTRA_TWS_PLUS_DEVICE2"; + + /** * Indicates the remote device is not bonded (paired). * <p>There is no shared link key with the remote device, so communication * (if it is allowed at all) will be unauthenticated and unencrypted. @@ -1293,10 +1438,12 @@ public final class BluetoothDevice implements Parcelable, Attributable { /*package*/ static IBluetooth getService() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + IBluetooth tService = adapter.getBluetoothService(sStateChangeCallback); + synchronized (BluetoothDevice.class) { if (sService == null) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - sService = adapter.getBluetoothService(sStateChangeCallback); + sService = tService; } } return sService; @@ -1307,9 +1454,10 @@ public final class BluetoothDevice implements Parcelable, Attributable { public void onBluetoothServiceUp(IBluetooth bluetoothService) throws RemoteException { synchronized (BluetoothDevice.class) { - if (sService == null) { - sService = bluetoothService; + if (sService != null) { + Log.w(TAG, "sService is not NULL"); } + sService = bluetoothService; } } @@ -1777,6 +1925,16 @@ public final class BluetoothDevice implements Parcelable, Attributable { final boolean defaultValue = false; if (service == null || !isBluetoothEnabled()) { Log.w(TAG, "BT not enabled, createBondOutOfBand failed"); + return false; + } + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && + (((transport == TRANSPORT_LE || transport == TRANSPORT_AUTO) + && !adapter.isLeEnabled()) + || ((transport == TRANSPORT_BREDR || transport == TRANSPORT_AUTO) + && !isBluetoothEnabled()))) { + Log.w(TAG, "creatBond() initiated in improper adapter state : " + adapter.getState() + + " transport = " + transport); if (DBG) log(Log.getStackTraceString(new Throwable())); } else if (NULL_MAC_ADDRESS.equals(mAddress)) { Log.e(TAG, "Unable to create bond, invalid address " + mAddress); @@ -1823,6 +1981,25 @@ public final class BluetoothDevice implements Parcelable, Attributable { return defaultValue; } + /** @hide */ + @UnsupportedAppUsage + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public void setBondingInitiatedLocally(boolean localInitiated) { + final IBluetooth service = sService; + if (service == null) { + Log.w(TAG, "BT not enabled, setBondingInitiatedLocally failed"); + return; + } + try { + service.setBondingInitiatedLocally(this, localInitiated, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return; + } + /** * Cancel an in-progress bonding request started with {@link #createBond}. * @@ -1870,6 +2047,12 @@ public final class BluetoothDevice implements Parcelable, Attributable { final boolean defaultValue = false; if (service == null || !isBluetoothEnabled()) { Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond"); + return false; + } + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && !adapter.isLeEnabled()) { + Log.w(TAG, "removeBond() initiated in improper adapter state : " + + adapter.getState()); if (DBG) log(Log.getStackTraceString(new Throwable())); } else { Log.i(TAG, "removeBond() for device " + getAddress() @@ -2344,6 +2527,47 @@ public final class BluetoothDevice implements Parcelable, Attributable { } /** + * Returns whether if the device is TWS+ device. + * + * @return True if the devcie is TWS+ device. + * @hide + */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public boolean isTwsPlusDevice() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot query remote device sdp records"); + return false; + } + try { + return sService.isTwsPlusDevice(this, mAttributionSource); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Get the TWS+ peer address of the remote device. + * + * @return the TWS+ peer address of the remote device if available, otherwise + * null. + * @hide + */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public String getTwsPlusPeerAddress() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Remote Device name"); + return null; + } + try { + return sService.getTwsPlusPeerAddress(this, mAttributionSource); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + + /** * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} * * @return true pin has been set false for error @@ -3075,6 +3299,48 @@ public final class BluetoothDevice implements Parcelable, Attributable { public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, boolean opportunistic, int phy, Handler handler) { + return connectGatt(context, autoConnect, callback, transport, opportunistic, + phy, handler, false); + } + + /** + * Connect to GATT Server hosted by this device. Caller acts as GATT client. + * The callback is used to deliver results to Caller, such as connection status as well + * as any further GATT client operations. + * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct + * GATT client operations. + * + * @param callback GATT callback handler that will receive asynchronous callbacks. + * @param autoConnect Whether to directly connect to the remote device (false) or to + * automatically connect as soon as the remote device becomes available (true). + * @param transport preferred transport for GATT connections to remote dual-mode devices {@link + * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link + * BluetoothDevice#TRANSPORT_LE} + * @param opportunistic Whether this GATT client is opportunistic. An opportunistic GATT client + * does not hold a GATT connection. It automatically disconnects when no other GATT connections + * are active for the remote device. + * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link + * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link + * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} + * is set to true. + * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on + * an un-specified background thread. + * @param eattSupport specifies whether client app needs EATT channel for client operations. + * If both local and remote devices support EATT and local app asks for EATT, GATT client + * operations will be performed using EATT channel. + * If either local or remote device doesn't support EATT but local App asks for EATT, GATT + * client operations will be performed using unenhanced ATT channel. + * + * @return A BluetoothGatt instance. You can use BluetoothGatt to conduct GATT client + * operations. + * + * @throws NullPointerException if callback is null + * + * @hide + */ + public BluetoothGatt connectGatt(Context context, boolean autoConnect, + BluetoothGattCallback callback, int transport, boolean opportunistic, + int phy, Handler handler, boolean eattSupport) { if (callback == null) { throw new NullPointerException("callback is null"); } @@ -3091,7 +3357,7 @@ public final class BluetoothDevice implements Parcelable, Attributable { } BluetoothGatt gatt = new BluetoothGatt( iGatt, this, transport, opportunistic, phy, mAttributionSource); - gatt.connect(autoConnect, callback, handler); + gatt.connect(autoConnect, callback, handler, eattSupport); return gatt; } catch (RemoteException e) { Log.e(TAG, "", e); @@ -3248,6 +3514,44 @@ public final class BluetoothDevice implements Parcelable, Attributable { } /** + * Returns Device type. + * + * @return device type. + * @hide + */ + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public int getDeviceType() { + if (sService == null) { + Log.e(TAG, "getDeviceType query remote device info failed"); + return -1; + } + try { + return sService.getDeviceType(this, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "getDeviceType fail ", e); + } + return -1; + } + + /** + * Used as a String extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. + * It contains the Group ID of IOT device. + * @hide + */ + public static final String EXTRA_GROUP_ID = "android.bluetooth.qti.extra.GROUP_ID"; + + /** + * Used as a String extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. + * It contains the IGNORE DEVICE flag of IOT device. + * @hide + */ + public static final String EXTRA_IS_PRIVATE_ADDRESS = + "android.bluetooth.qti.extra.IS_PRIVATE_ADDRESS"; + + /** * Enable or disable audio low latency for this {@link BluetoothDevice}. * * @param allowed true if low latency is allowed, false if low latency is disallowed. diff --git a/framework/java/android/bluetooth/BluetoothGatt.java b/framework/java/android/bluetooth/BluetoothGatt.java index ee9c691134..edfd8ba91f 100644 --- a/framework/java/android/bluetooth/BluetoothGatt.java +++ b/framework/java/android/bluetooth/BluetoothGatt.java @@ -12,6 +12,41 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Changes from Qualcomm Innovation Center are provided under the following license: + * + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) 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 Qualcomm Innovation Center, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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; @@ -150,6 +185,27 @@ public final class BluetoothGatt implements BluetoothProfile { public static final int CONNECTION_PRIORITY_LOW_POWER = 2; /** + * Connection subrate request - Balanced. + * + * @hide + */ + public static final int SUBRATE_REQ_BALANCED = 0; + + /** + * Connection subrate request - High. + * + * @hide + */ + public static final int SUBRATE_REQ_HIGH = 1; + + /** + * Connection Subrate Request - Low Power. + * + * @hide + */ + public static final int SUBRATE_REQ_LOW_POWER = 2; + + /** * No authentication required. * * @hide @@ -769,6 +825,34 @@ public final class BluetoothGatt implements BluetoothProfile { } }); } + + /** + * Callback invoked when the given connection's subrate is changed + * @hide + */ + @Override + public void onSubrateChange(String address, int subrateFactor, int latency, + int contNum, int timeout, int status) { + Log.d(TAG, "onSubrateChange() - Device=" + address + + " subrateFactor=" + subrateFactor + " latency=" + latency + + " contNum=" + contNum + " timeout=" + timeout + " status=" + status); + + if (!address.equals(mDevice.getAddress())) { + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onSubrateChange(BluetoothGatt.this, subrateFactor, latency, + contNum, timeout, status); + } + } + }); + } + }; /* package */ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport, @@ -972,9 +1056,45 @@ public final class BluetoothGatt implements BluetoothProfile { @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, Handler handler) { + return connect(autoConnect, callback, handler, false); + } + + /** + * Initiate a connection to a Bluetooth GATT capable device. + * + * <p>The connection may not be established right away, but will be + * completed when the remote device is available. A + * {@link BluetoothGattCallback#onConnectionStateChange} callback will be + * invoked when the connection state changes as a result of this function. + * + * <p>The autoConnect parameter determines whether to actively connect to + * the remote device, or rather passively scan and finalize the connection + * when the remote device is in range/available. Generally, the first ever + * connection to a device should be direct (autoConnect set to false) and + * subsequent connections to known devices should be invoked with the + * autoConnect parameter set to true. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param device Remote device to connect to + * @param autoConnect Whether to directly connect to the remote device (false) or to + * automatically connect as soon as the remote device becomes available (true). + * @param eattSupport specifies whether client app needs EATT channel for client operations. + * If both local and remote devices support EATT and local app asks for EATT, GATT client + * operations will be performed using EATT channel. + * If either local or remote device doesn't support EATT but local App asks for EATT, GATT + * client operations will be performed using unenhanced ATT channel. + * @return true, if the connection attempt was initiated successfully + * + * @hide + */ + @UnsupportedAppUsage + /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, + Handler handler, boolean eattSupport) { if (DBG) { Log.d(TAG, - "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect); + "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect + + ", eattSupport: " + eattSupport); } synchronized (mStateLock) { if (mConnState != CONN_STATE_IDLE) { @@ -985,7 +1105,7 @@ public final class BluetoothGatt implements BluetoothProfile { mAutoConnect = autoConnect; - if (!registerApp(callback, handler)) { + if (!registerApp(callback, handler, eattSupport)) { synchronized (mStateLock) { mConnState = CONN_STATE_IDLE; } @@ -1875,6 +1995,70 @@ public final class BluetoothGatt implements BluetoothProfile { } /** + * Request LE subrate mode. + * + * <p>This function will send a LE subrate request to the + * remote device. + * + * @param subrateMode Request a specific subrate mode. Must be one of {@link + * BluetoothGatt#SUBRATE_REQ_BALANCED}, {@link BluetoothGatt#SUBRATE_REQ_HIGH} + * or {@link BluetoothGatt#SUBRATE_REQ_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their specified range. + * @hide + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public boolean requestSubrateMode(int subrateMode) { + if (subrateMode < SUBRATE_REQ_BALANCED + || subrateMode > SUBRATE_REQ_LOW_POWER) { + throw new IllegalArgumentException(" Subrate Mode not within valid range"); + } + + Log.d(TAG, "requestsubrateMode() - params: " + subrateMode); + if (mService == null || mClientIf == 0) return false; + + try { + mService.subrateModeRequest( + mClientIf, mDevice.getAddress(), subrateMode, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + + return true; + } + + /** + * Request a LE subrate request. + * + * <p>This function will send a LE subrate request to the remote device. + * + * @return true, if the request is send to the Bluetooth stack. + * @hide + */ + public boolean bleSubrateRequest(int subrateMin, int subrateMax, + int maxLatency, int contNumber, + int supervisionTimeout) { + Log.d(TAG, "bleSubrateRequest() - subrateMin=" + subrateMin + + " subrateMax=" + (subrateMax) + + " maxLatency= " + maxLatency + "contNumber=" + + contNumber + " supervisionTimeout=" + supervisionTimeout); + if (mService == null || mClientIf == 0) return false; + + try { + mService.leSubrateRequest(mClientIf, mDevice.getAddress(), + subrateMin, subrateMax, maxLatency, + contNumber, supervisionTimeout, + mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + + return true; + } + + /** * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} * with {@link BluetoothProfile#GATT} as argument * @throws UnsupportedOperationException diff --git a/framework/java/android/bluetooth/BluetoothGattCallback.java b/framework/java/android/bluetooth/BluetoothGattCallback.java index 3852d508c0..5d8b047394 100644 --- a/framework/java/android/bluetooth/BluetoothGattCallback.java +++ b/framework/java/android/bluetooth/BluetoothGattCallback.java @@ -12,6 +12,41 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Changes from Qualcomm Innovation Center are provided under the following license: + * + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) 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 Qualcomm Innovation Center, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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; @@ -267,4 +302,22 @@ public abstract class BluetoothGattCallback { */ public void onServiceChanged(@NonNull BluetoothGatt gatt) { } + + /** + * Callback indicating LE connection's subrate parameters have changed. + * + * @param gatt GATT client involved + * @param subrate factor for the LE connection. + * @param peripheral latency for the LE connection in number of subrated connection events. + * Valid range is from 0 to 499 + * @param continuation number. Valid range is from 0 to 499. + * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 + * (0.1s) to 3200 (32s) + * @param status {@link BluetoothGatt#GATT_SUCCESS} if LE connection subrating has been changed + * successfully. + * @hide + */ + public void onSubrateChange(BluetoothGatt gatt, int subrateFactor, int latency, int contNum, + int timeout, int status) { + } } diff --git a/framework/java/android/bluetooth/BluetoothGattServer.java b/framework/java/android/bluetooth/BluetoothGattServer.java index 8b84505e36..e2410aab96 100644 --- a/framework/java/android/bluetooth/BluetoothGattServer.java +++ b/framework/java/android/bluetooth/BluetoothGattServer.java @@ -12,6 +12,41 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Changes from Qualcomm Innovation Center are provided under the following license: + * + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) 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 Qualcomm Innovation Center, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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; @@ -389,6 +424,27 @@ public final class BluetoothGattServer implements BluetoothProfile { } } + /** + * Callback invoked when the given connection's subrate parameters are changed + * @hide + */ + @Override + public void onSubrateChange(String address, int subrateFactor, int latency, + int contNum, int timeout, int status) { + Log.d(TAG, "onSubrateChange() - Device=" + address + + " subrateFactor=" + subrateFactor + " latency=" + latency + + " contNum=" + contNum + " timeout=" + timeout + " status=" + status); + BluetoothDevice device = mAdapter.getRemoteDevice(address); + if (device == null) return; + + try { + mCallback.onSubrateChange(device, subrateFactor, latency, + contNum, timeout, status); + } catch (Exception ex) { + Log.w(TAG, "Unhandled exception: " + ex); + } + } + }; /** diff --git a/framework/java/android/bluetooth/BluetoothGattServerCallback.java b/framework/java/android/bluetooth/BluetoothGattServerCallback.java index 0ead5f57e8..f07ce3e13f 100644 --- a/framework/java/android/bluetooth/BluetoothGattServerCallback.java +++ b/framework/java/android/bluetooth/BluetoothGattServerCallback.java @@ -12,6 +12,41 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * Changes from Qualcomm Innovation Center are provided under the following license: + * + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) 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 Qualcomm Innovation Center, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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; @@ -199,4 +234,22 @@ public abstract class BluetoothGattServerCallback { int status) { } + /** + * Callback indicating the LE connection's subrate parameters were updated. + * + * @param device The remote device involved + * @param subrate factor for the LE connection. + * @param peripheral latency for the LE connection in number of subrated connection events. + * Valid range is from 0 to 499. + * @param continuation number. Valid range is from 0 to 499. + * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 + * (0.1s) to 3200 (32s) + * @param status {@link BluetoothGatt#GATT_SUCCESS} if LE connection subrating has been changed + * successfully. + * @hide + */ + public void onSubrateChange(BluetoothDevice device, int subrateFactor, int latency, int contNum, + int timeout, int status) { + } + } diff --git a/framework/java/android/bluetooth/BluetoothHeadset.java b/framework/java/android/bluetooth/BluetoothHeadset.java index 434542a5e9..693b86608c 100644 --- a/framework/java/android/bluetooth/BluetoothHeadset.java +++ b/framework/java/android/bluetooth/BluetoothHeadset.java @@ -43,12 +43,14 @@ import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.modules.utils.SynchronousResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.Objects; import java.util.concurrent.TimeoutException; @@ -277,6 +279,13 @@ public final class BluetoothHeadset implements BluetoothProfile { public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; /** + * Headset state when SCO audio is disconnecting. + * + * @hide + */ + public static final int STATE_AUDIO_DISCONNECTING = 13; + + /** * Headset state when SCO audio is not connected. * This state can be one of * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of @@ -301,6 +310,41 @@ public final class BluetoothHeadset implements BluetoothProfile { public static final int STATE_AUDIO_CONNECTED = 12; /** + * Intent used to broadcast the Battery status of TWS+ devices + * + * <p>This intent will have 2 extras: + * <ul> + * <li> {@link #EXTRA_HF_TWSP_BATTERY_STATE} - Current Battey state of TWS+ + * device. 0 for Discharging, 1 for Charging + * <\li> + * <li> {@link #EXTRA_HF_TWSP_BATTERY_LEVEL} - Current Battey charging level + * in percentage of TWS+ device. + * <\li> + * + * @hide + */ + public static final String ACTION_HF_TWSP_BATTERY_STATE_CHANGED = + "android.bluetooth.headset.action.HF_TWSP_BATTERY_STATE_CHANGED"; + + /** + * A int extra field in {@link #EXTRA_HF_TWSP_BATTERY_STATE} + * intents that contains the battery state of TWS+ device + * + * @hide + */ + public static final String EXTRA_HF_TWSP_BATTERY_STATE = + "android.bluetooth.headset.extra.HF_TWSP_BATTERY_STATE"; + + /** + * A int extra field in {@link #EXTRA_HF_TWSP_BATTERY_LEVEL} + * intents that contains the value of battery level in percentage for TWS+ device + * @hide + */ + public static final String EXTRA_HF_TWSP_BATTERY_LEVEL = + "android.bluetooth.headset.extra.HF_TWSP_BATTERY_LEVEL"; + + + /** * Intent used to broadcast the headset's indicator status * * <p>This intent will have 3 extras: @@ -351,7 +395,8 @@ public final class BluetoothHeadset implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private volatile IBluetoothHeadset mService; + private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); + @GuardedBy("mServiceLock") private IBluetoothHeadset mService; private final BluetoothAdapter mAdapter; private final AttributionSource mAttributionSource; @@ -402,7 +447,7 @@ public final class BluetoothHeadset implements BluetoothProfile { private boolean doBind() { synchronized (mConnection) { if (mService == null) { - if (VDBG) Log.d(TAG, "Binding service..."); + if (DBG) Log.d(TAG, "Binding service..."); try { return mAdapter.getBluetoothManager().bindBluetoothProfileService( BluetoothProfile.HEADSET, mConnection); @@ -416,15 +461,17 @@ public final class BluetoothHeadset implements BluetoothProfile { private void doUnbind() { synchronized (mConnection) { + if (DBG) Log.d(TAG, "Unbinding service..."); if (mService != null) { - if (VDBG) Log.d(TAG, "Unbinding service..."); try { mAdapter.getBluetoothManager().unbindBluetoothProfileService( BluetoothProfile.HEADSET, mConnection); } catch (RemoteException e) { Log.e(TAG, "Unable to unbind HeadsetService", e); } finally { + mServiceLock.writeLock().lock(); mService = null; + mServiceLock.writeLock().unlock(); } } } @@ -557,24 +604,29 @@ public final class BluetoothHeadset implements BluetoothProfile { @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - final IBluetoothHeadset service = mService; - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { + try { + mServiceLock.readLock().lock(); + final IBluetoothHeadset service = mService; + final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (isEnabled()) { try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevicesWithAttribution(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + final SynchronousResultReceiver<List<BluetoothDevice>> recv = + new SynchronousResultReceiver(); + service.getConnectedDevicesWithAttribution(mAttributionSource, recv); + return Attributable.setAttributionSource( + recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), + mAttributionSource); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } } + return defaultValue; + } finally { + mServiceLock.readLock().unlock(); } - return defaultValue; } /** @@ -874,17 +926,23 @@ public final class BluetoothHeadset implements BluetoothProfile { if (VDBG) log("isAudioConnected()"); final IBluetoothHeadset service = mService; final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAudioConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + try { + mServiceLock.readLock().lock(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (isEnabled() && isValidDevice(device)) { + try { + final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); + service.isAudioConnected(device, mAttributionSource, recv); + return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); } + } finally { + mServiceLock.readLock().unlock(); } return defaultValue; } @@ -1498,17 +1556,26 @@ public final class BluetoothHeadset implements BluetoothProfile { @Override public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); - mService = IBluetoothHeadset.Stub.asInterface(service); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_CONNECTED)); + try { + mServiceLock.writeLock().lock(); + mService = IBluetoothHeadset.Stub.asInterface(service); + mHandler.sendMessage(mHandler.obtainMessage( + MESSAGE_HEADSET_SERVICE_CONNECTED)); + } finally { + mServiceLock.writeLock().unlock(); + } } @Override public void onServiceDisconnected(ComponentName className) { if (DBG) Log.d(TAG, "Proxy object disconnected"); - doUnbind(); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_DISCONNECTED)); + try { + mServiceLock.writeLock().lock(); + mHandler.sendMessage(mHandler.obtainMessage( + MESSAGE_HEADSET_SERVICE_DISCONNECTED)); + } finally { + mServiceLock.writeLock().unlock(); + } } }; @@ -1550,4 +1617,59 @@ public final class BluetoothHeadset implements BluetoothProfile { } } }; + + /** + * Notify Headset of phone state change. + * This is a backdoor for phone app to call BluetoothHeadset since + * there is currently not a good way to get precise call state change outside + * of phone app. + * + * @hide + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) + public void phoneStateChangedDsDa(int numActive, int numHeld, int callState, String number, + int type, String name) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { + try { + service.phoneStateChangedDsDa(numActive, numHeld, callState, number, type, name, + mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + + /** + * Send Headset of CLCC response + * + * @hide + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) + public void clccResponseDsDa(int index, int direction, int status, int mode, boolean mpty, + String number, int type) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { + try { + service.clccResponseDsDa(index, direction, status, mode, mpty, number, type, + mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } } diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java index 6d9fa07d51..4346dae6da 100644 --- a/framework/java/android/bluetooth/BluetoothLeAudio.java +++ b/framework/java/android/bluetooth/BluetoothLeAudio.java @@ -62,8 +62,8 @@ import java.util.concurrent.TimeoutException; */ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { private static final String TAG = "BluetoothLeAudio"; - private static final boolean DBG = false; - private static final boolean VDBG = false; + private static final boolean DBG = true; + private static final boolean VDBG = true; private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>(); diff --git a/framework/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/framework/java/android/bluetooth/BluetoothLeAudioCodecConfig.java index c91d13ffe0..314bb3d1ca 100644 --- a/framework/java/android/bluetooth/BluetoothLeAudioCodecConfig.java +++ b/framework/java/android/bluetooth/BluetoothLeAudioCodecConfig.java @@ -39,12 +39,18 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { /** @hide */ @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { SOURCE_CODEC_TYPE_LC3, + SOURCE_CODEC_TYPE_APTX_ADAPTIVE_LE, SOURCE_CODEC_TYPE_INVALID }) @Retention(RetentionPolicy.SOURCE) public @interface SourceCodecType {}; public static final int SOURCE_CODEC_TYPE_LC3 = 0; + /** + * AptX Adaptive LEA Codec. + * @hide + */ + public static final int SOURCE_CODEC_TYPE_APTX_ADAPTIVE_LE = 1; public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; /** @hide */ @@ -74,7 +80,7 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { /** @hide */ @IntDef(flag = true, prefix = "SAMPLE_RATE_", value = {SAMPLE_RATE_NONE, SAMPLE_RATE_8000, SAMPLE_RATE_16000, SAMPLE_RATE_24000, - SAMPLE_RATE_32000, SAMPLE_RATE_44100, SAMPLE_RATE_48000}) + SAMPLE_RATE_32000, SAMPLE_RATE_44100, SAMPLE_RATE_48000, SAMPLE_RATE_96000}) @Retention(RetentionPolicy.SOURCE) public @interface SampleRate {} @@ -118,6 +124,12 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { */ public static final int SAMPLE_RATE_48000 = 0x01 << 7; + /** + * Codec sample rate 96000 Hz. + * @hide + */ + public static final int SAMPLE_RATE_96000 = 0x01 << 9; + /** @hide */ @IntDef(flag = true, prefix = "BITS_PER_SAMPLE_", value = {BITS_PER_SAMPLE_NONE, BITS_PER_SAMPLE_16, BITS_PER_SAMPLE_24, @@ -178,9 +190,11 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { * Bluetooth Assigned Numbers, Generic Audio, * Supported_Frame_Durations table * + * Vendor Specific frame duration starting from bit 16 * @hide */ @IntDef(flag = true, prefix = "FRAME_DURATION_", - value = {FRAME_DURATION_NONE, FRAME_DURATION_7500, FRAME_DURATION_10000}) + value = {FRAME_DURATION_NONE, FRAME_DURATION_7500, + FRAME_DURATION_10000, FRAME_DURATION_15000}) @Retention(RetentionPolicy.SOURCE) public @interface FrameDuration {} @@ -199,6 +213,13 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { */ public static final int FRAME_DURATION_10000 = 0x01 << 1; + /** + * Frame duration 15000 us. + * @hide + */ + public static final int FRAME_DURATION_15000 = 0x01 << 16; + + private final @SourceCodecType int mCodecType; private final @CodecPriority int mCodecPriority; private final @SampleRate int mSampleRate; @@ -208,7 +229,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { private final int mOctetsPerFrame; private final int mMinOctetsPerFrame; private final int mMaxOctetsPerFrame; - + private final long mCodecSpecific1; + private final long mCodecSpecific2; + private final long mCodecSpecific3; + private final long mCodecSpecific4; /** * Creates a new BluetoothLeAudioCodecConfig. @@ -222,12 +246,17 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { * @param octetsPerFrame the octets per frame of this codec * @param minOctetsPerFrame the minimum octets per frame of this codec * @param maxOctetsPerFrame the maximum octets per frame of this codec + * @param codecSpecific1 the specific value 1 + * @param codecSpecific2 the specific value 2 + * @param codecSpecific3 the specific value 3 + * @param codecSpecific4 the specific value 4 */ private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority, @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, @ChannelCount int channelCount, @FrameDuration int frameDuration, int octetsPerFrame, - int minOctetsPerFrame, int maxOctetsPerFrame) { + int minOctetsPerFrame, int maxOctetsPerFrame, long codecSpecific1, + long codecSpecific2, long codecSpecific3, long codecSpecific4) { mCodecType = codecType; mCodecPriority = codecPriority; mSampleRate = sampleRate; @@ -237,6 +266,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { mOctetsPerFrame = octetsPerFrame; mMinOctetsPerFrame = minOctetsPerFrame; mMaxOctetsPerFrame = maxOctetsPerFrame; + mCodecSpecific1 = codecSpecific1; + mCodecSpecific2 = codecSpecific2; + mCodecSpecific3 = codecSpecific3; + mCodecSpecific4 = codecSpecific4; } @Override @@ -260,9 +293,14 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { int octetsPerFrame = in.readInt(); int minOctetsPerFrame = in.readInt(); int maxOctetsPerFrame = in.readInt(); + long codecSpecific1 = in.readLong(); + long codecSpecific2 = in.readLong(); + long codecSpecific3 = in.readLong(); + long codecSpecific4 = in.readLong(); return new BluetoothLeAudioCodecConfig(codecType, codecPriority, sampleRate, bitsPerSample, channelCount, frameDuration, octetsPerFrame, - minOctetsPerFrame, maxOctetsPerFrame); + minOctetsPerFrame, maxOctetsPerFrame, codecSpecific1, codecSpecific2, + codecSpecific3, codecSpecific4); } public BluetoothLeAudioCodecConfig[] newArray(int size) { @@ -281,6 +319,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { out.writeInt(mOctetsPerFrame); out.writeInt(mMinOctetsPerFrame); out.writeInt(mMaxOctetsPerFrame); + out.writeLong(mCodecSpecific1); + out.writeLong(mCodecSpecific2); + out.writeLong(mCodecSpecific3); + out.writeLong(mCodecSpecific4); } @Override @@ -290,7 +332,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { + ",mBitsPerSample:" + mBitsPerSample + ",mChannelCount:" + mChannelCount + ",mFrameDuration:" + mFrameDuration + ",mOctetsPerFrame:" + mOctetsPerFrame + ",mMinOctetsPerFrame:" + mMinOctetsPerFrame - + ",mMaxOctetsPerFrame:" + mMaxOctetsPerFrame + "}"; + + ",mMaxOctetsPerFrame:" + mMaxOctetsPerFrame + + ",mCodecSpecific1:" + mCodecSpecific1 + ",mCodecSpecific2:" + mCodecSpecific2 + + ",mCodecSpecific3:" + mCodecSpecific3 + ",mCodecSpecific4:" + mCodecSpecific4 + + "}"; } /** @@ -311,6 +356,8 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { switch (mCodecType) { case SOURCE_CODEC_TYPE_LC3: return "LC3"; + case SOURCE_CODEC_TYPE_APTX_ADAPTIVE_LE: + return "APTX_ADAPTIVE_LEA"; case SOURCE_CODEC_TYPE_INVALID: return "INVALID CODEC"; default: @@ -377,6 +424,46 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { return mMaxOctetsPerFrame; } + /** + * Returns the codec specific value1. + * As the value and usage differ for each codec, please refer to the concerned + * codec specification to obtain the codec specific information. + * @hide + */ + public long getCodecSpecific1() { + return mCodecSpecific1; + } + + /** + * Returns the codec specific value2. + * As the value and usage differ for each codec, please refer to the concerned + * codec specification to obtain the codec specific information. + * @hide + */ + public long getCodecSpecific2() { + return mCodecSpecific2; + } + + /** + * Returns the codec specific value3. + * As the value and usage differ for each codec, please refer to the concerned + * codec specification to obtain the codec specific information. + * @hide + */ + public long getCodecSpecific3() { + return mCodecSpecific3; + } + + /** + * Returns the codec specific value4. + * As the value and usage differ for each codec, please refer to the concerned + * codec specification to obtain the codec specific information. + * @hide + */ + public long getCodecSpecific4() { + return mCodecSpecific4; + } + @Override public boolean equals(@NonNull Object o) { if (o instanceof BluetoothLeAudioCodecConfig) { @@ -389,7 +476,11 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { && other.getFrameDuration() == mFrameDuration && other.getOctetsPerFrame() == mOctetsPerFrame && other.getMinOctetsPerFrame() == mMinOctetsPerFrame - && other.getMaxOctetsPerFrame() == mMaxOctetsPerFrame); + && other.getMaxOctetsPerFrame() == mMaxOctetsPerFrame + && other.mCodecSpecific1 == mCodecSpecific1 + && other.mCodecSpecific2 == mCodecSpecific2 + && other.mCodecSpecific3 == mCodecSpecific3 + && other.mCodecSpecific4 == mCodecSpecific4); } return false; } @@ -402,7 +493,8 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { public int hashCode() { return Objects.hash(mCodecType, mCodecPriority, mSampleRate, mBitsPerSample, mChannelCount, mFrameDuration, mOctetsPerFrame, - mMinOctetsPerFrame, mMaxOctetsPerFrame); + mMinOctetsPerFrame, mMaxOctetsPerFrame, mCodecSpecific1, + mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); } /** @@ -420,6 +512,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { private int mOctetsPerFrame = 0; private int mMinOctetsPerFrame = 0; private int mMaxOctetsPerFrame = 0; + private long mCodecSpecific1 = 0; + private long mCodecSpecific2 = 0; + private long mCodecSpecific3 = 0; + private long mCodecSpecific4 = 0; public Builder() {} @@ -433,6 +529,10 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { mOctetsPerFrame = config.getOctetsPerFrame(); mMinOctetsPerFrame = config.getMinOctetsPerFrame(); mMaxOctetsPerFrame = config.getMaxOctetsPerFrame(); + mCodecSpecific1 = config.getCodecSpecific1(); + mCodecSpecific2 = config.getCodecSpecific2(); + mCodecSpecific3 = config.getCodecSpecific3(); + mCodecSpecific4 = config.getCodecSpecific4(); } /** @@ -535,13 +635,62 @@ public final class BluetoothLeAudioCodecConfig implements Parcelable { } /** + * Set the codecspecific1 for Bluetooth LE audio codec config. + * + * @param codecspecific1 of this codec + * @return the same Builder instance + * @hide + */ + public @NonNull Builder setCodecSpecific1(long codecSpecific1) { + mCodecSpecific1 = codecSpecific1; + return this; + } + + /** + * Set the codecspecific2 for Bluetooth LE audio codec config. + * + * @param codecspecific2 of this codec + * @return the same Builder instance + * @hide + */ + public @NonNull Builder setCodecSpecific2(long codecSpecific2) { + mCodecSpecific2 = codecSpecific2; + return this; + } + + /** + * Set the codecspecific3 for Bluetooth LE audio codec config. + * + * @param codecspecific3 of this codec + * @return the same Builder instance + * @hide + */ + public @NonNull Builder setCodecSpecific3(long codecSpecific3) { + mCodecSpecific3 = codecSpecific3; + return this; + } + + /** + * Set the codecspecific4 for Bluetooth LE audio codec config. + * + * @param codecspecific4 of this codec + * @return the same Builder instance + * @hide + */ + public @NonNull Builder setCodecSpecific4(long codecSpecific4) { + mCodecSpecific4 = codecSpecific4; + return this; + } + + /** * Build {@link BluetoothLeAudioCodecConfig}. * @return new BluetoothLeAudioCodecConfig built */ public @NonNull BluetoothLeAudioCodecConfig build() { return new BluetoothLeAudioCodecConfig(mCodecType, mCodecPriority, mSampleRate, mBitsPerSample, mChannelCount, mFrameDuration, mOctetsPerFrame, - mMinOctetsPerFrame, mMaxOctetsPerFrame); + mMinOctetsPerFrame, mMaxOctetsPerFrame, mCodecSpecific1, + mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); } } } diff --git a/framework/java/android/bluetooth/BluetoothProfile.java b/framework/java/android/bluetooth/BluetoothProfile.java index 5c80a9bdde..4b8b52b6da 100644 --- a/framework/java/android/bluetooth/BluetoothProfile.java +++ b/framework/java/android/bluetooth/BluetoothProfile.java @@ -1,4 +1,4 @@ -/* + /* * Copyright (C) 2010-2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -274,12 +274,55 @@ public interface BluetoothProfile { int BATTERY = 30; /** + * DUN + * @hide + */ + public int DUN = 31; + + /** + * Group Operation Profile (Client Role) + * @hide + */ + public int GROUP_CLIENT = 32; + + /** + * Broadcast + * @hide + */ + public int BROADCAST = 33; + + /** + * VCP + * @hide + */ + public static final int VCP = 34; + + /** + * BC_PROFILE + * @hide + */ + public static final int BC_PROFILE = 35; + + /** + * PC_PROFILE + * @hide + */ + public static final int PC_PROFILE = 36; + + /** + * CC_SERVER + * @hide + */ + public static final int CC_SERVER = 37; + + + /** * 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 = 30; + int MAX_PROFILE_ID = 37; /** * Default priority for devices that we try to auto-connect to and @@ -501,6 +544,20 @@ public interface BluetoothProfile { return "LE_AUDIO_BROADCAST_ASSISTANT"; case BATTERY: return "BATTERY"; + case BROADCAST: + return "BROADCAST"; + case VCP: + return "VCP"; + case GROUP_CLIENT: + return "GROUP_CLIENT"; + case DUN: + return "DUN"; + case BC_PROFILE: + return "BC_PROFILE"; + case PC_PROFILE: + return "PC_PROFILE"; + case CC_SERVER: + return "CC_SERVER"; default: return "UNKNOWN_PROFILE"; } diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java index bf98d97aed..c137f66cef 100644 --- a/framework/java/android/bluetooth/BluetoothSocket.java +++ b/framework/java/android/bluetooth/BluetoothSocket.java @@ -298,6 +298,7 @@ public final class BluetoothSocket implements Closeable { as.mSocketOS = as.mSocket.getOutputStream(); as.mAddress = remoteAddr; as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr); + as.mPort = mPort; return as; } @@ -845,5 +846,65 @@ public final class BluetoothSocket implements Closeable { return ret; } + /** + * setSocketOpt for the Buetooth Socket. + * + * @param optionName socket option name + * @param optionVal socket option value + * @param optionLen socket option length + * @return -1 on immediate error, + * 0 otherwise + * @hide + */ + @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public int setSocketOpt(int optionName, byte [] optionVal, int optionLen) throws IOException { + int ret = 0; + if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + Log.e(TAG, "setSocketOpt fail, reason: bluetooth is off"); + return -1; + } + try { + if(VDBG) Log.d(TAG, "setSocketOpt(), mType: " + mType + " mPort: " + mPort); + ret = bluetoothProxy.setSocketOpt(mType, mPort, optionName, optionVal, optionLen); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return -1; + } + return ret; + } + + /** + * getSocketOpt for the Buetooth Socket. + * + * @param optionName socket option name + * @param optionVal socket option value + * @return -1 on immediate error, + * length of returned socket option otherwise + * @hide + */ + @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public int getSocketOpt(int optionName, byte [] optionVal) throws IOException { + int ret = 0; + if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + Log.e(TAG, "getSocketOpt fail, reason: bluetooth is off"); + return -1; + } + try { + if(VDBG) Log.d(TAG, "getSocketOpt(), mType: " + mType + " mPort: " + mPort); + ret = bluetoothProxy.getSocketOpt(mType, mPort, optionName, optionVal); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return -1; + } + return ret; + } } diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java index 63f293ae66..77e518b1d0 100644 --- a/framework/java/android/bluetooth/BluetoothUuid.java +++ b/framework/java/android/bluetooth/BluetoothUuid.java @@ -378,6 +378,41 @@ public final class BluetoothUuid { public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_HEARINGAID_UUID = + ParcelUuid.fromString("00006AD2-0000-1000-8000-00805F9B34FB"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_MEDIA_T_UUID = + ParcelUuid.fromString("00006AD0-0000-1000-8000-00805F9B34FB"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_MEDIA_P_UUID = + ParcelUuid.fromString("00006AD1-0000-1000-8000-00805F9B34FB"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_MEDIA_G_UUID = + ParcelUuid.fromString("00006AD3-0000-1000-8000-00805F9B34FB"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_MEDIA_W_UUID = + ParcelUuid.fromString("2587db3c-ce70-4fc9-935f-777ab4188fd7"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_VOICE_P_UUID = + ParcelUuid.fromString("00006AD4-0000-1000-8000-00805F9B34FB"); + + /** @hide */ + @NonNull + public static ParcelUuid ADVANCE_VOICE_T_UUID = + ParcelUuid.fromString("00006AD5-0000-1000-8000-00805F9B34FB"); + /** * Length of bytes for 16 bit UUID * diff --git a/framework/java/android/bluetooth/DeviceGroup.java b/framework/java/android/bluetooth/DeviceGroup.java new file mode 100644 index 0000000000..0dac87f3d8 --- /dev/null +++ b/framework/java/android/bluetooth/DeviceGroup.java @@ -0,0 +1,177 @@ +/****************************************************************************** + * 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; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * 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 CopyOnWriteArrayList <BluetoothDevice> mGroupDevices + = new CopyOnWriteArrayList<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.addAll(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/framework/java/android/bluetooth/le/AdvertisingSetParameters.java b/framework/java/android/bluetooth/le/AdvertisingSetParameters.java index 5c8fae6519..ecc2d7ccfe 100644 --- a/framework/java/android/bluetooth/le/AdvertisingSetParameters.java +++ b/framework/java/android/bluetooth/le/AdvertisingSetParameters.java @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.Parcelable; +import android.app.ActivityThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -444,10 +445,20 @@ public final class AdvertisingSetParameters implements Parcelable { * {@link AdvertisingSetParameters#TX_POWER_MEDIUM}, * or {@link AdvertisingSetParameters#TX_POWER_HIGH}. * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. + * Allow tx power level to be set more than {@link AdvertisingSetParameters#TX_POWER_HIGH}, + * if the setTxPowerLevel is invoked from com.android.bluetooth process */ public Builder setTxPowerLevel(int txPowerLevel) { - if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) { - throw new IllegalArgumentException("unknown txPowerLevel " + txPowerLevel); + String packageName = ActivityThread.currentPackageName(); + if (packageName.equals("com.android.bluetooth.services")) { + int maxPowerLevel = 20; + if (txPowerLevel < TX_POWER_MIN || txPowerLevel > maxPowerLevel) { + throw new IllegalArgumentException("invalid txPowerLevel " + txPowerLevel); + } + } else { + if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) { + throw new IllegalArgumentException("unknown txPowerLevel " + txPowerLevel); + } } mTxPowerLevel = txPowerLevel; return this; diff --git a/framework/java/android/bluetooth/le/BluetoothLeScanner.java b/framework/java/android/bluetooth/le/BluetoothLeScanner.java index f5f963e21d..890c259642 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/framework/java/android/bluetooth/le/BluetoothLeScanner.java @@ -42,6 +42,7 @@ import android.util.Log; import com.android.modules.utils.SynchronousResultReceiver; +import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -264,6 +265,13 @@ public final class BluetoothLeScanner { if (gatt == null) { return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); } + + if ((settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_SENSOR_ROUTING) + && (filters == null || filters.isEmpty())) { + ScanFilter filter = (new ScanFilter.Builder()).build(); + filters = Arrays.asList(filter); + } + if (!isSettingsConfigAllowedForScan(settings)) { return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); @@ -276,6 +284,10 @@ public final class BluetoothLeScanner { return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); } + if (!isRoutingAllowedForScan(settings)) { + return postCallbackErrorOrReturn(callback, + ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); + } if (callback != null) { BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, settings, workSource, callback); @@ -675,4 +687,14 @@ public final class BluetoothLeScanner { } return true; } + + private boolean isRoutingAllowedForScan(ScanSettings settings) { + final int callbackType = settings.getCallbackType(); + + if (callbackType == ScanSettings.CALLBACK_TYPE_SENSOR_ROUTING + && settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC) { + return false; + } + return true; + } } diff --git a/framework/java/android/bluetooth/le/BluetoothLeUtils.java b/framework/java/android/bluetooth/le/BluetoothLeUtils.java index a600e7a62e..943ee534d5 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeUtils.java +++ b/framework/java/android/bluetooth/le/BluetoothLeUtils.java @@ -91,6 +91,28 @@ public class BluetoothLeUtils { } /** + * Returns a string composed from a byte array. + */ + static <T> String toString(byte[] data) { + if (data == null) { + return "null"; + } + if (data.length == 0) { + return "{}"; + } + StringBuilder buffer = new StringBuilder(); + buffer.append('{'); + for(int i=0; i < data.length; i++) { + buffer.append(data[i]); + if ((i+1) < data.length) { + buffer.append(", "); + } + } + buffer.append('}'); + return buffer.toString(); + } + + /** * Check whether two {@link SparseArray} equal. */ static boolean equals(SparseArray<byte[]> array, SparseArray<byte[]> otherArray) { @@ -140,6 +162,25 @@ public class BluetoothLeUtils { } /** + * Check whether two byte arrays are equal. + */ + static <T> boolean equals(byte[] data, byte[] otherData) { + if (data == otherData) { + return true; + } + if (data == null || otherData == null) { + return false; + } + if (data.length != otherData.length) { + return false; + } + if (!Objects.deepEquals(data, otherData)) { + return false; + } + return true; + } + + /** * Ensure Bluetooth is turned on. * * @throws IllegalStateException If {@code adapter} is null or Bluetooth state is not {@link diff --git a/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java index 13b7163f30..e8e1d8737b 100644 --- a/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java +++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java @@ -351,6 +351,9 @@ public final class PeriodicAdvertisingManager { @Override public void run() { callback.onSyncTransferred(device, status); + // App can still unregister the sync until notified it's lost. + // Remove callback after app was notifed. + //mCallbackWrappers.remove(callback); } }); } diff --git a/framework/java/android/bluetooth/le/ScanFilter.java b/framework/java/android/bluetooth/le/ScanFilter.java index bf902e889a..b72e7ef31a 100644 --- a/framework/java/android/bluetooth/le/ScanFilter.java +++ b/framework/java/android/bluetooth/le/ScanFilter.java @@ -51,6 +51,12 @@ import java.util.UUID; */ public final class ScanFilter implements Parcelable { + /** + * Provide TDS data scan results for WiFi Alliance Org id + * @hide + */ + public static final int WIFI_ALLIANCE_ORG_ID = 2; + @Nullable private final String mDeviceName; @@ -91,6 +97,15 @@ public final class ScanFilter implements Parcelable { @Nullable private final byte[] mAdvertisingDataMask; + private final int mOrgId; + private final int mTDSFlags; + 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(); @@ -99,7 +114,9 @@ public final class ScanFilter implements Parcelable { ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, @AddressType int addressType, @Nullable byte[] irk, int advertisingDataType, - @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask) { + @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask, + int orgId, int TDSFlags, int TDSFlagsMask, byte[] wifiNANHash, + boolean groupBasedFiltering) { mDeviceName = name; mServiceUuid = uuid; mServiceUuidMask = uuidMask; @@ -117,6 +134,11 @@ public final class ScanFilter implements Parcelable { mAdvertisingDataType = advertisingDataType; mAdvertisingData = advertisingData; mAdvertisingDataMask = advertisingDataMask; + mOrgId = orgId; + mTDSFlags = TDSFlags; + mTDSFlagsMask = TDSFlagsMask; + mWifiNANHash = wifiNANHash; + mGroupBasedFiltering = groupBasedFiltering; } @Override @@ -200,6 +222,19 @@ public final class ScanFilter implements Parcelable { dest.writeByteArray(mAdvertisingDataMask); } } + + dest.writeInt(mOrgId); + dest.writeInt(mOrgId < 0 ? 0 : 1); + if(mOrgId >= 0) { + dest.writeInt(mTDSFlags); + dest.writeInt(mTDSFlagsMask); + dest.writeInt(mWifiNANHash == null ? 0 : 1); + if (mWifiNANHash != null) { + dest.writeInt(mWifiNANHash.length); + dest.writeByteArray(mWifiNANHash); + } + } + dest.writeBoolean(mGroupBasedFiltering); } /** @@ -309,6 +344,24 @@ public final class ScanFilter implements Parcelable { advertisingDataMask); } + int orgId = in.readInt(); + if(in.readInt() == 1) { + int tdsFlags = in.readInt(); + int tdsFlagsMask = in.readInt(); + if (in.readInt() == 1) { + int wifiNANHashLength = in.readInt(); + byte[] wifiNanHash = new byte[wifiNANHashLength]; + in.readByteArray(wifiNanHash); + builder.setTransportDiscoveryData(orgId, tdsFlags, tdsFlagsMask, + wifiNanHash); + } + else { + builder.setTransportDiscoveryData(orgId, tdsFlags, tdsFlagsMask, null); + } + } + + boolean groupBasedFiltering = in.readBoolean(); + builder.setGroupBasedFiltering(groupBasedFiltering); return builder.build(); } }; @@ -405,6 +458,45 @@ public final class ScanFilter implements Parcelable { } /** + * @hide + * Returns the organization id. -1 if the organization id is not set. + */ + public int getOrgId() { + return mOrgId; + } + + /** + * @hide + * Returns the TDS flags. -1 if TDS flags is not set. + */ + public int getTDSFlags() { + return mTDSFlags; + } + + /** + * @hide + * Returns the TDS flags mask. -1 if TDS flags mask is not set. + */ + public int getTDSFlagsMask() { + return mTDSFlagsMask; + } + + /** + * @hide + */ + public byte[] getWifiNANHash() { + return mWifiNANHash; + } + + /** + * @hide + * Returns true, if Group AD Type based filtering is enabled. Otherwise, false. + */ + public boolean getGroupFilteringValue() { + return mGroupBasedFiltering; + } + + /** * Returns the advertising data type of this filter. * Returns {@link ScanRecord#DATA_TYPE_NONE} if the type is not set. * The values of advertising data type are defined in the Bluetooth Generic Access Profile @@ -497,6 +589,25 @@ public final class ScanFilter implements Parcelable { } } + //Transport Discovery data match + if(mOrgId >= 0) { + byte[] tdsData = scanRecord.getTDSData(); + if ((tdsData != null) && (tdsData.length > 0)) { + if ((mOrgId != tdsData[0]) || + ((mTDSFlags & mTDSFlagsMask) != (tdsData[1] & mTDSFlagsMask))) { + return false; + } + } + } + + // Group AD Type filter match + if (mGroupBasedFiltering) { + byte [] groupIdData = scanRecord.getGroupIdentifierData(); + if (groupIdData != null && groupIdData.length != GROUP_DATA_LEN) { + return false; + } + } + // All filters match. return true; } @@ -593,7 +704,11 @@ public final class ScanFilter implements Parcelable { + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + ", mAdvertisingDataType=" + mAdvertisingDataType + ", mAdvertisingData=" + Arrays.toString(mAdvertisingData) + ", mAdvertisingDataMask=" - + Arrays.toString(mAdvertisingDataMask) + "]"; + + Arrays.toString(mAdvertisingDataMask) + + ", mOrganizationId=" + mOrgId + ", mTDSFlags=" + mTDSFlags + + ", mTDSFlagsMask=" + mTDSFlagsMask + + ", mWifiNANHash=" + Arrays.toString(mWifiNANHash) +"]" + + ", mGroupBasedFiltering=" + mGroupBasedFiltering; } @Override @@ -608,7 +723,10 @@ public final class ScanFilter implements Parcelable { mServiceSolicitationUuid, mServiceSolicitationUuidMask, mAdvertisingDataType, Arrays.hashCode(mAdvertisingData), - Arrays.hashCode(mAdvertisingDataMask)); + Arrays.hashCode(mAdvertisingDataMask), + mServiceSolicitationUuid, mServiceSolicitationUuidMask, + mOrgId, mTDSFlags, mTDSFlagsMask, Arrays.hashCode(mWifiNANHash), + mGroupBasedFiltering); } @Override @@ -635,7 +753,12 @@ public final class ScanFilter implements Parcelable { other.mServiceSolicitationUuidMask) && mAdvertisingDataType == other.mAdvertisingDataType && Objects.deepEquals(mAdvertisingData, other.mAdvertisingData) - && Objects.deepEquals(mAdvertisingDataMask, other.mAdvertisingDataMask); + && Objects.deepEquals(mAdvertisingDataMask, other.mAdvertisingDataMask) + && mOrgId == other.mOrgId + && mTDSFlags == other.mTDSFlags + && mTDSFlagsMask == other.mTDSFlagsMask + && Objects.deepEquals(mWifiNANHash, other.mWifiNANHash) + && mGroupBasedFiltering == other.mGroupBasedFiltering; } /** @@ -681,6 +804,13 @@ public final class ScanFilter implements Parcelable { private byte[] mAdvertisingData; private byte[] mAdvertisingDataMask; + private int mOrgId = -1; + private int mTDSFlags = -1; + private int mTDSFlagsMask = -1; + private byte[] mWifiNANHash; + + private boolean mGroupBasedFiltering; + /** * Set filter on device name. */ @@ -987,6 +1117,38 @@ public final class ScanFilter implements Parcelable { return this; } + + /** + * @hide + * Set filter on transport discovery data. + * @throws IllegalArgumentException If the {@code orgId} is invalid or {@code + * wifiNANhash} is not null while {@code orgId} is non-Wifi. + */ + public Builder setTransportDiscoveryData(int orgId, int TDSFlags, int TDSFlagsMask, + byte[] wifiNANHash) { + if (orgId < 0) { + throw new IllegalArgumentException("invalid organization id"); + } + if ((orgId != WIFI_ALLIANCE_ORG_ID) && (wifiNANHash != null)) { + throw new IllegalArgumentException("Wifi NAN Hash is not null for non-Wifi Org Id"); + } + mOrgId = orgId; + mTDSFlags = TDSFlags; + mTDSFlagsMask = TDSFlagsMask; + mWifiNANHash = wifiNANHash; + return this; + } + + /** + * @hide + * Enable filter on Group AD Type. + */ + public @NonNull Builder setGroupBasedFiltering( + boolean enable) { + mGroupBasedFiltering = enable; + return this; + } + /** * Set filter on advertising data with specific advertising data type. * For any bit in the mask, set it the 1 if it needs to match the one in @@ -1053,7 +1215,9 @@ public final class ScanFilter implements Parcelable { mServiceSolicitationUuid, mServiceSolicitationUuidMask, mServiceDataUuid, mServiceData, mServiceDataMask, mManufacturerId, mManufacturerData, mManufacturerDataMask, mAddressType, mIrk, mAdvertisingDataType, - mAdvertisingData, mAdvertisingDataMask); + mAdvertisingData, mAdvertisingDataMask, + mOrgId, mTDSFlags, mTDSFlagsMask, mWifiNANHash, + mGroupBasedFiltering); } } } diff --git a/framework/java/android/bluetooth/le/ScanRecord.java b/framework/java/android/bluetooth/le/ScanRecord.java index 375df1d694..ca2fd457ad 100644 --- a/framework/java/android/bluetooth/le/ScanRecord.java +++ b/framework/java/android/bluetooth/le/ScanRecord.java @@ -313,6 +313,10 @@ public final class ScanRecord { * details. */ public static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; + /** + * @hide + */ + public static int DATA_TYPE_GROUP_AD_TYPE = 0x00; // Flags of the advertising data. private final int mAdvertiseFlags; @@ -337,6 +341,12 @@ public final class ScanRecord { private final HashMap<Integer, byte[]> mAdvertisingDataMap; + // 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. @@ -431,6 +441,22 @@ public final class ScanRecord { } /** + * @hide + * Returns Transport Discovery data + */ + public byte[] getTDSData() { + return mTDSData; + } + + /** + * @hide + * Returns Group Identifier data + */ + public byte[] getGroupIdentifierData() { + return mGroupIdentifierData; + } + + /** * Returns raw bytes of scan record. */ public byte[] getBytes() { @@ -463,7 +489,8 @@ public final class ScanRecord { SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData, int advertiseFlags, int txPowerLevel, - String localName, HashMap<Integer, byte[]> advertisingDataMap, byte[] bytes) { + String localName, HashMap<Integer, byte[]> advertisingDataMap, + byte[] tdsData, byte[] groupIdentifierData, byte[] bytes) { mServiceSolicitationUuids = serviceSolicitationUuids; mServiceUuids = serviceUuids; mManufacturerSpecificData = manufacturerData; @@ -472,6 +499,8 @@ public final class ScanRecord { mAdvertiseFlags = advertiseFlags; mTxPowerLevel = txPowerLevel; mAdvertisingDataMap = advertisingDataMap; + mTDSData = tdsData; + mGroupIdentifierData = groupIdentifierData; mBytes = bytes; } @@ -503,6 +532,9 @@ public final class ScanRecord { Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>(); HashMap<Integer, byte[]> advertisingDataMap = new HashMap<Integer, byte[]>(); + byte[] tdsData = null; + byte[] groupIdentifierData = null; + try { while (currentPos < scanRecord.length) { // length is unsigned int. @@ -582,8 +614,15 @@ public final class ScanRecord { dataLength - 2); manufacturerData.put(manufacturerId, manufacturerDataBytes); break; + 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; @@ -594,13 +633,13 @@ public final class ScanRecord { } return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData, serviceData, advertiseFlag, txPowerLevel, localName, advertisingDataMap, - scanRecord); + 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, - advertisingDataMap, scanRecord); + advertisingDataMap, null, null, scanRecord); } } @@ -611,7 +650,8 @@ public final class ScanRecord { + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString( mManufacturerSpecificData) + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData) - + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; + + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + + ", mTDSData=" + BluetoothLeUtils.toString(mTDSData) +"]"; } // Parse service UUIDs. diff --git a/framework/java/android/bluetooth/le/ScanResult.java b/framework/java/android/bluetooth/le/ScanResult.java index f437d867ea..90a429460b 100644 --- a/framework/java/android/bluetooth/le/ScanResult.java +++ b/framework/java/android/bluetooth/le/ScanResult.java @@ -93,6 +93,7 @@ public final class ScanResult implements Parcelable, Attributable { private int mAdvertisingSid; private int mTxPower; private int mPeriodicAdvertisingInterval; + private int mAddressType; /** * Constructs a new ScanResult. @@ -117,6 +118,7 @@ public final class ScanResult implements Parcelable, Attributable { mAdvertisingSid = SID_NOT_PRESENT; mTxPower = 127; mPeriodicAdvertisingInterval = 0; + mAddressType = -1; } /** @@ -146,6 +148,42 @@ public final class ScanResult implements Parcelable, Attributable { mPeriodicAdvertisingInterval = periodicAdvertisingInterval; mScanRecord = scanRecord; mTimestampNanos = timestampNanos; + mAddressType = -1; + } + + /** + * Constructs a new ScanResult. + * + * @param device Remote Bluetooth device found. + * @param addressType addressType for the Scan result + * @param eventType Event type. + * @param primaryPhy Primary advertising phy. + * @param secondaryPhy Secondary advertising phy. + * @param advertisingSid Advertising set ID. + * @param txPower Transmit power. + * @param rssi Received signal strength. + * @param periodicAdvertisingInterval Periodic advertising interval. + * @param scanRecord Scan record including both advertising data and scan response data. + * @param timestampNanos Timestamp at which the scan result was observed. + * @param addressType addressType for the Scan result + * + *@hide + */ + public ScanResult(BluetoothDevice device, int addressType, int eventType, int primaryPhy, + int secondaryPhy, + int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, + ScanRecord scanRecord, long timestampNanos) { + mDevice = device; + mEventType = eventType; + mPrimaryPhy = primaryPhy; + mSecondaryPhy = secondaryPhy; + mAdvertisingSid = advertisingSid; + mTxPower = txPower; + mRssi = rssi; + mPeriodicAdvertisingInterval = periodicAdvertisingInterval; + mScanRecord = scanRecord; + mTimestampNanos = timestampNanos; + mAddressType = addressType; } private ScanResult(Parcel in) { @@ -174,6 +212,7 @@ public final class ScanResult implements Parcelable, Attributable { dest.writeInt(mAdvertisingSid); dest.writeInt(mTxPower); dest.writeInt(mPeriodicAdvertisingInterval); + dest.writeInt(mAddressType); } private void readFromParcel(Parcel in) { @@ -191,6 +230,7 @@ public final class ScanResult implements Parcelable, Attributable { mAdvertisingSid = in.readInt(); mTxPower = in.readInt(); mPeriodicAdvertisingInterval = in.readInt(); + mAddressType = in.readInt(); } @Override @@ -308,6 +348,14 @@ public final class ScanResult implements Parcelable, Attributable { return mPeriodicAdvertisingInterval; } + /** + * + *@hide + */ + public int getAddressType() { + return mAddressType; + } + @Override public int hashCode() { return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos, @@ -333,7 +381,8 @@ public final class ScanResult implements Parcelable, Attributable { && mSecondaryPhy == other.mSecondaryPhy && mAdvertisingSid == other.mAdvertisingSid && mTxPower == other.mTxPower - && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval; + && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval + && mAddressType == other.mAddressType; } @Override diff --git a/framework/java/android/bluetooth/le/ScanSettings.java b/framework/java/android/bluetooth/le/ScanSettings.java index 1f9d4caec5..8f99170662 100644 --- a/framework/java/android/bluetooth/le/ScanSettings.java +++ b/framework/java/android/bluetooth/le/ScanSettings.java @@ -96,6 +96,12 @@ public final class ScanSettings implements Parcelable { */ public static final int CALLBACK_TYPE_MATCH_LOST = 4; + /** + * Provide results to sensor router instead of the apps processor + * @hide + */ + public static final int CALLBACK_TYPE_SENSOR_ROUTING = 8; + /** * Determines how many advertisements to match per filter, as this is scarce hw resource @@ -338,7 +344,8 @@ public final class ScanSettings implements Parcelable { private boolean isValidCallbackType(int callbackType) { if (callbackType == CALLBACK_TYPE_ALL_MATCHES || callbackType == CALLBACK_TYPE_FIRST_MATCH - || callbackType == CALLBACK_TYPE_MATCH_LOST) { + || callbackType == CALLBACK_TYPE_MATCH_LOST + || callbackType == CALLBACK_TYPE_SENSOR_ROUTING) { return true; } return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST); |