Merge "Hearing Aid Profile: set and get activeDevice" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-03-23 18:22:27 +00:00
committed by Android (Google) Code Review
7 changed files with 196 additions and 0 deletions

View File

@@ -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
*

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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