From 88f36dccc7f945f77b12312b564549a9091be1bf Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Wed, 31 Aug 2022 01:39:20 +0800 Subject: [PATCH] Fix after dis/reconnect HA, DUT will request to re-pair one side of HA Root Cause: Another side of HA jumps to connecting state after pair-other-ear dialog appears. Solution: Listen to onDeviceAttributesChanged() callback to know when the sub device gets connected, then to dismiss dialog. Bug: 236782832 Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidUtilsTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidPairingDialogFragmentTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=AvailableMediaDeviceGroupControllerTest Change-Id: I8dafefbb05e7cf1cadf37a7acbb2c5d66f2d0a78 --- .../accessibility/HearingAidUtils.java | 2 +- .../HearingAidPairingDialogFragment.java | 58 ++++++++++++--- ...ityHearingAidPreferenceControllerTest.java | 1 + .../HearingAidPairingDialogFragmentTest.java | 70 ++++++++++++++++--- .../accessibility/HearingAidUtilsTest.java | 40 ++++++++++- ...ailableMediaDeviceGroupControllerTest.java | 14 +++- 6 files changed, 162 insertions(+), 23 deletions(-) diff --git a/src/com/android/settings/accessibility/HearingAidUtils.java b/src/com/android/settings/accessibility/HearingAidUtils.java index a3d2c93f438..dcbac5b2b90 100644 --- a/src/com/android/settings/accessibility/HearingAidUtils.java +++ b/src/com/android/settings/accessibility/HearingAidUtils.java @@ -53,7 +53,7 @@ public final class HearingAidUtils { Log.w(TAG, "Can not launch hearing aid pairing dialog for invalid side"); return; } - HearingAidPairingDialogFragment.newInstance(device).show(fragmentManager, + HearingAidPairingDialogFragment.newInstance(device.getAddress()).show(fragmentManager, HearingAidPairingDialogFragment.TAG); } } diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java index 60a100d538a..5ae7c1711d8 100644 --- a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java +++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java @@ -18,6 +18,9 @@ package com.android.settings.bluetooth; import android.app.Dialog; import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; import android.os.Bundle; import androidx.annotation.NonNull; @@ -29,29 +32,59 @@ import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; /** * Provides a dialog to pair another side of hearing aid device. */ -public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment { +public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment implements + CachedBluetoothDevice.Callback { public static final String TAG = "HearingAidPairingDialogFragment"; - private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side"; + private static final String KEY_DEVICE_ADDRESS = "device_address"; + private LocalBluetoothManager mLocalBluetoothManager; + private CachedBluetoothDevice mDevice; /** * Creates a new {@link HearingAidPairingDialogFragment} and shows pair another side of hearing - * aid device according to {@code CachedBluetoothDevice} side. + * aid device according to {@code deviceAddress}. * - * @param device The remote Bluetooth device, that needs to be hearing aid device. + * @param deviceAddress The remote Bluetooth device address, that needs to be a hearing aid + * device. * @return a DialogFragment */ - public static HearingAidPairingDialogFragment newInstance(CachedBluetoothDevice device) { + public static HearingAidPairingDialogFragment newInstance(String deviceAddress) { Bundle args = new Bundle(1); - args.putInt(KEY_CACHED_DEVICE_SIDE, device.getDeviceSide()); + args.putString(KEY_DEVICE_ADDRESS, deviceAddress); final HearingAidPairingDialogFragment fragment = new HearingAidPairingDialogFragment(); fragment.setArguments(args); return fragment; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + mLocalBluetoothManager = Utils.getLocalBtManager(context); + mDevice = getDevice(); + if (mDevice != null) { + mDevice.registerCallback(this); + } + } + + @Override + public void onDetach() { + super.onDetach(); + if (mDevice != null) { + mDevice.unregisterCallback(this); + } + } + + private CachedBluetoothDevice getDevice() { + final String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS); + final BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + deviceAddress); + return mLocalBluetoothManager.getCachedDeviceManager().findDevice(device); + } + @Override public int getMetricsCategory() { return SettingsEnums.DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER; @@ -60,7 +93,7 @@ public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - final int deviceSide = getArguments().getInt(KEY_CACHED_DEVICE_SIDE); + final int deviceSide = mDevice.getDeviceSide(); final int titleId = R.string.bluetooth_pair_other_ear_dialog_title; final int messageId = (deviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT) ? R.string.bluetooth_pair_other_ear_dialog_left_ear_message @@ -72,8 +105,7 @@ public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment return new AlertDialog.Builder(getActivity()) .setTitle(titleId) .setMessage(messageId) - .setNegativeButton( - android.R.string.cancel, /* listener= */ null) + .setNegativeButton(android.R.string.cancel, /* listener= */ null) .setPositiveButton(pairBtnId, (dialog, which) -> positiveButtonListener()) .create(); } @@ -84,4 +116,12 @@ public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment .setSourceMetricsCategory(getMetricsCategory()) .launch(); } + + @Override + public void onDeviceAttributesChanged() { + final CachedBluetoothDevice subDevice = mDevice.getSubDevice(); + if (subDevice != null && subDevice.isConnectedHearingAidDevice()) { + this.dismiss(); + } + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java index affb28be97d..94483150caa 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java @@ -265,6 +265,7 @@ public class AccessibilityHearingAidPreferenceControllerTest { mBluetoothAdapter.enable(); mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME); } diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java index 5990a3db2a7..bda60d49995 100644 --- a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java @@ -18,24 +18,38 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.bluetooth.BluetoothPairingDetail; import com.android.settings.bluetooth.HearingAidPairingDialogFragment; +import com.android.settings.bluetooth.Utils; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Rule; @@ -47,39 +61,57 @@ import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; /** Tests for {@link HearingAidPairingDialogFragment}. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowAlertDialogCompat.class) +@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class, + ShadowBluetoothUtils.class}) public class HearingAidPairingDialogFragmentTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); - private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side"; + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + private final Context mContext = ApplicationProvider.getApplicationContext(); @Mock private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private CachedBluetoothDevice mCachedSubBluetoothDevice; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + private BluetoothAdapter mBluetoothAdapter; private FragmentActivity mActivity; private HearingAidPairingDialogFragment mFragment; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private BluetoothDevice mBluetoothDevice; + private FragmentManager mFragmentManager; @Before public void setUp() { - mFragment = spy(HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice)); + setupEnvironment(); + mFragment = spy(HearingAidPairingDialogFragment.newInstance(TEST_DEVICE_ADDRESS)); mActivity = Robolectric.setupActivity(FragmentActivity.class); + mFragmentManager = mActivity.getSupportFragmentManager(); when(mFragment.getActivity()).thenReturn(mActivity); + doReturn(mFragmentManager).when(mFragment).getParentFragmentManager(); + mFragment.onAttach(mContext); } @Test public void newInstance_deviceSideRight_argumentSideRight() { when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( HearingAidProfile.DeviceSide.SIDE_RIGHT); + final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY); + dialog.show(); - mFragment = HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice); - - final Bundle bundle = mFragment.getArguments(); - assertThat(bundle.getInt(KEY_CACHED_DEVICE_SIDE)).isEqualTo( - HearingAidProfile.DeviceSide.SIDE_RIGHT); + final String pairLeftString = mContext.getText( + R.string.bluetooth_pair_other_ear_dialog_left_ear_positive_button).toString(); + assertThat(dialog.getButton( + DialogInterface.BUTTON_POSITIVE).getText().toString()).isEqualTo(pairLeftString); } @Test @@ -109,4 +141,26 @@ public class HearingAidPairingDialogFragmentTest { assertThat(mFragment.getMetricsCategory()).isEqualTo( SettingsEnums.DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER); } + + @Test + public void onDeviceAttributesChanged_subHearingAidDeviceConnected_dialogDismiss() { + when(mCachedSubBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mCachedSubBluetoothDevice); + + mFragment.onDeviceAttributesChanged(); + + verify(mFragment).dismiss(); + } + + private void setupEnvironment() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java index efeb27f0686..6fdd210a860 100644 --- a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java @@ -20,13 +20,24 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; +import androidx.test.core.app.ApplicationProvider; +import com.android.settings.bluetooth.Utils; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Rule; @@ -38,27 +49,40 @@ import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; /** Tests for {@link HearingAidUtils}. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowAlertDialogCompat.class) +@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class, + ShadowBluetoothUtils.class}) public class HearingAidUtilsTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock private CachedBluetoothDevice mSubCachedBluetoothDevice; - + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + private BluetoothDevice mBluetoothDevice; + private BluetoothAdapter mBluetoothAdapter; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; private FragmentManager mFragmentManager; @Before public void setUp() { + setupEnvironment(); final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class); mFragmentManager = mActivity.getSupportFragmentManager(); ShadowAlertDialogCompat.reset(); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); } @Test @@ -123,4 +147,16 @@ public class HearingAidUtilsTest { final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog.isShowing()).isTrue(); } + + private void setupEnvironment() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java index e1a3da6329a..2b6250396b1 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.PackageManager; @@ -50,6 +51,7 @@ import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -70,6 +72,7 @@ import org.robolectric.annotation.Config; ShadowBluetoothUtils.class}) public class AvailableMediaDeviceGroupControllerTest { + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String PREFERENCE_KEY_1 = "pref_key_1"; @Mock @@ -85,7 +88,9 @@ public class AvailableMediaDeviceGroupControllerTest { @Mock private BluetoothEventManager mEventManager; @Mock - private LocalBluetoothManager mLocalManager; + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @@ -93,7 +98,6 @@ public class AvailableMediaDeviceGroupControllerTest { private Context mContext; private Preference mPreference; private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController; - private LocalBluetoothManager mLocalBluetoothManager; private AudioManager mAudioManager; @Before @@ -112,10 +116,14 @@ public class AvailableMediaDeviceGroupControllerTest { when(mDashboardFragment.getParentFragmentManager()).thenReturn( mActivity.getSupportFragmentManager()); - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mAudioManager = mContext.getSystemService(AudioManager.class); doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn( + mCachedBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); mAvailableMediaDeviceGroupController = spy( new AvailableMediaDeviceGroupController(mContext));