Merge "Hearing Aid Profile: set and get activeDevice" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9753e774ce
@@ -17,6 +17,7 @@
|
||||
package android.bluetooth;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
@@ -378,6 +379,76 @@ public final class BluetoothHearingAid implements BluetoothProfile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a connected device as active.
|
||||
*
|
||||
* The active device selection is per profile. An active device's
|
||||
* purpose is profile-specific. For example, Hearing Aid audio
|
||||
* streaming is to the active Hearing Aid device. If a remote device
|
||||
* is not connected, it cannot be selected as active.
|
||||
*
|
||||
* <p> This API returns false in scenarios like the profile on the
|
||||
* device is not connected or Bluetooth is not turned on.
|
||||
* When this API returns true, it is guaranteed that the
|
||||
* {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
|
||||
* with the active device.
|
||||
*
|
||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
|
||||
* permission.
|
||||
*
|
||||
* @param device the remote Bluetooth device. Could be null to clear
|
||||
* the active device and stop streaming audio to a Bluetooth device.
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
|
||||
if (DBG) log("setActiveDevice(" + device + ")");
|
||||
try {
|
||||
mServiceLock.readLock().lock();
|
||||
if (mService != null && isEnabled()
|
||||
&& ((device == null) || isValidDevice(device))) {
|
||||
mService.setActiveDevice(device);
|
||||
return true;
|
||||
}
|
||||
if (mService == null) Log.w(TAG, "Proxy not attached to service");
|
||||
return false;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
|
||||
return false;
|
||||
} finally {
|
||||
mServiceLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the device is active.
|
||||
*
|
||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
||||
* permission.
|
||||
*
|
||||
* @return the connected device that is active or null if no device
|
||||
* is active
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH)
|
||||
public boolean isActiveDevice(@Nullable BluetoothDevice device) {
|
||||
if (VDBG) log("isActiveDevice()");
|
||||
try {
|
||||
mServiceLock.readLock().lock();
|
||||
if (mService != null && isEnabled()
|
||||
&& ((device == null) || isValidDevice(device))) {
|
||||
return mService.isActiveDevice(device);
|
||||
}
|
||||
if (mService == null) Log.w(TAG, "Proxy not attached to service");
|
||||
return false;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
|
||||
return false;
|
||||
} finally {
|
||||
mServiceLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set priority of the profile
|
||||
*
|
||||
|
||||
@@ -179,6 +179,8 @@
|
||||
android:name="android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED" />
|
||||
<protected-broadcast
|
||||
android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
|
||||
<protected-broadcast
|
||||
android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
|
||||
<protected-broadcast
|
||||
android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
|
||||
<protected-broadcast
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothHearingAid;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -115,6 +116,8 @@ public class BluetoothEventManager {
|
||||
new ActiveDeviceChangedHandler());
|
||||
addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
|
||||
new ActiveDeviceChangedHandler());
|
||||
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
|
||||
new ActiveDeviceChangedHandler());
|
||||
|
||||
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
|
||||
mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
|
||||
@@ -434,6 +437,8 @@ public class BluetoothEventManager {
|
||||
bluetoothProfile = BluetoothProfile.A2DP;
|
||||
} else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
|
||||
bluetoothProfile = BluetoothProfile.HEADSET;
|
||||
} else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
|
||||
bluetoothProfile = BluetoothProfile.HEARING_AID;
|
||||
} else {
|
||||
Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
|
||||
return;
|
||||
|
||||
@@ -109,6 +109,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
// Active device state
|
||||
private boolean mIsActiveDeviceA2dp = false;
|
||||
private boolean mIsActiveDeviceHeadset = false;
|
||||
private boolean mIsActiveDeviceHearingAid = false;
|
||||
|
||||
/**
|
||||
* Describes the current device and profile for logging.
|
||||
@@ -436,6 +437,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
|
||||
if ((hearingAidProfile != null) && isConnectedProfile(hearingAidProfile)) {
|
||||
if (hearingAidProfile.setActiveDevice(getDevice())) {
|
||||
Log.i(TAG, "OnPreferenceClickListener: Hearing Aid active device=" + this);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -501,6 +509,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
changed = (mIsActiveDeviceHeadset != isActive);
|
||||
mIsActiveDeviceHeadset = isActive;
|
||||
break;
|
||||
case BluetoothProfile.HEARING_AID:
|
||||
changed = (mIsActiveDeviceHearingAid != isActive);
|
||||
mIsActiveDeviceHearingAid = isActive;
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "onActiveDeviceChanged: unknown profile " + bluetoothProfile +
|
||||
" isActive " + isActive);
|
||||
@@ -524,6 +536,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
return mIsActiveDeviceA2dp;
|
||||
case BluetoothProfile.HEADSET:
|
||||
return mIsActiveDeviceHeadset;
|
||||
case BluetoothProfile.HEARING_AID:
|
||||
return mIsActiveDeviceHearingAid;
|
||||
default:
|
||||
Log.w(TAG, "getActiveDevice: unknown profile " + bluetoothProfile);
|
||||
break;
|
||||
@@ -615,6 +629,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
if (headsetProfile != null) {
|
||||
mIsActiveDeviceHeadset = mDevice.equals(headsetProfile.getActiveDevice());
|
||||
}
|
||||
HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
|
||||
if (hearingAidProfile != null) {
|
||||
mIsActiveDeviceHearingAid = hearingAidProfile.isActiveDevice(mDevice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -945,6 +963,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
boolean profileConnected = false; // at least one profile is connected
|
||||
boolean a2dpNotConnected = false; // A2DP is preferred but not connected
|
||||
boolean hfpNotConnected = false; // HFP is preferred but not connected
|
||||
boolean hearingAidNotConnected = false; // Hearing Aid is preferred but not connected
|
||||
|
||||
for (LocalBluetoothProfile profile : getProfiles()) {
|
||||
int connectionStatus = getProfileConnectionState(profile);
|
||||
@@ -966,6 +985,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
} else if ((profile instanceof HeadsetProfile) ||
|
||||
(profile instanceof HfpClientProfile)) {
|
||||
hfpNotConnected = true;
|
||||
} else if (profile instanceof HearingAidProfile) {
|
||||
hearingAidNotConnected = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -998,6 +1019,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
|
||||
activeDeviceString = activeDeviceStringsArray[3]; // Active for Phone only
|
||||
}
|
||||
}
|
||||
if (!hearingAidNotConnected && mIsActiveDeviceHearingAid) {
|
||||
activeDeviceString = activeDeviceStringsArray[1];
|
||||
return mContext.getString(R.string.bluetooth_connected, activeDeviceString);
|
||||
}
|
||||
|
||||
if (profileConnected) {
|
||||
if (a2dpNotConnected && hfpNotConnected) {
|
||||
|
||||
@@ -134,6 +134,17 @@ public class HearingAidProfile implements LocalBluetoothProfile {
|
||||
return mService.getConnectionState(device);
|
||||
}
|
||||
|
||||
public boolean setActiveDevice(BluetoothDevice device) {
|
||||
if (mService == null) return false;
|
||||
mService.setActiveDevice(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isActiveDevice(BluetoothDevice device) {
|
||||
if (mService == null) return false;
|
||||
return mService.isActiveDevice(device);
|
||||
}
|
||||
|
||||
public boolean isPreferred(BluetoothDevice device) {
|
||||
if (mService == null) return false;
|
||||
return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
|
||||
|
||||
@@ -71,6 +71,8 @@ public class CachedBluetoothDeviceManagerTest {
|
||||
@Mock
|
||||
private PanProfile mPanProfile;
|
||||
@Mock
|
||||
private HearingAidProfile mHearingAidProfile;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice1;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice2;
|
||||
@@ -100,6 +102,7 @@ public class CachedBluetoothDeviceManagerTest {
|
||||
when(mHfpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mA2dpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mPanProfile.isProfileReady()).thenReturn(true);
|
||||
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
|
||||
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
|
||||
}
|
||||
|
||||
@@ -280,4 +283,63 @@ public class CachedBluetoothDeviceManagerTest {
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify onActiveDeviceChanged() with A2DP and Hearing Aid.
|
||||
*/
|
||||
@Test
|
||||
public void testOnActiveDeviceChanged_withA2dpAndHearingAid() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
|
||||
mLocalProfileManager, mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
|
||||
mLocalProfileManager, mDevice2);
|
||||
assertThat(cachedDevice2).isNotNull();
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
|
||||
// Connect device1 for A2DP and HFP and device2 for Hearing Aid
|
||||
cachedDevice1.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
cachedDevice1.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
// Verify that both devices are connected and none is Active
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
|
||||
// The first device is active for A2DP and HFP
|
||||
mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1, BluetoothProfile.A2DP);
|
||||
mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1, BluetoothProfile.HEADSET);
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
|
||||
// The second device is active for Hearing Aid and the first device is not active
|
||||
mCachedDeviceManager.onActiveDeviceChanged(null, BluetoothProfile.A2DP);
|
||||
mCachedDeviceManager.onActiveDeviceChanged(null, BluetoothProfile.HEADSET);
|
||||
mCachedDeviceManager.onActiveDeviceChanged(cachedDevice2, BluetoothProfile.HEARING_AID);
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue();
|
||||
|
||||
// No active device for Hearing Aid
|
||||
mCachedDeviceManager.onActiveDeviceChanged(null, BluetoothProfile.HEARING_AID);
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ public class CachedBluetoothDeviceTest {
|
||||
@Mock
|
||||
private PanProfile mPanProfile;
|
||||
@Mock
|
||||
private HearingAidProfile mHearingAidProfile;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice;
|
||||
private CachedBluetoothDevice mCachedDevice;
|
||||
private Context mContext;
|
||||
@@ -70,6 +72,7 @@ public class CachedBluetoothDeviceTest {
|
||||
when(mHfpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mA2dpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mPanProfile.isProfileReady()).thenReturn(true);
|
||||
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
|
||||
mCachedDevice = spy(
|
||||
new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice));
|
||||
doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
|
||||
@@ -203,6 +206,23 @@ public class CachedBluetoothDeviceTest {
|
||||
assertThat(mCachedDevice.getConnectionSummary()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConnectionSummary_testSingleProfileActiveDeviceHearingAid() {
|
||||
// Test without battery level
|
||||
// Set Hearing Aid profile to be connected and test connection state summary
|
||||
mCachedDevice.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
|
||||
|
||||
// Set device as Active for Hearing Aid and test connection state summary
|
||||
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
|
||||
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active");
|
||||
|
||||
// Set Hearing Aid profile to be disconnected and test connection state summary
|
||||
mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEARING_AID);
|
||||
mCachedDevice.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
|
||||
assertThat(mCachedDevice.getConnectionSummary()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConnectionSummary_testMultipleProfilesActiveDevice() {
|
||||
// Test without battery level
|
||||
|
||||
Reference in New Issue
Block a user