diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java index 2cb74262f0a..3dde687328c 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java @@ -38,6 +38,7 @@ import com.android.settings.R; import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SubSettingLauncher; +import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; @@ -54,7 +55,7 @@ import java.util.concurrent.FutureTask; * Controller that shows and updates the bluetooth device name */ public class AccessibilityHearingAidPreferenceController extends BasePreferenceController - implements LifecycleObserver, OnStart, OnStop { + implements LifecycleObserver, OnStart, OnStop, BluetoothCallback { private static final String TAG = "AccessibilityHearingAidPreferenceController"; private Preference mHearingAidPreference; @@ -109,11 +110,13 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); mContext.registerReceiver(mHearingAidChangedReceiver, filter); + mLocalBluetoothManager.getEventManager().registerCallback(this); } @Override public void onStop() { mContext.unregisterReceiver(mHearingAidChangedReceiver); + mLocalBluetoothManager.getEventManager().unregisterCallback(this); } @Override @@ -159,6 +162,17 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC R.string.accessibility_hearingaid_right_side_device_summary, name); } + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { + if (activeDevice == null) { + return; + } + + if (bluetoothProfile == BluetoothProfile.HEARING_AID) { + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice); + } + } + public void setFragmentManager(FragmentManager fragmentManager) { mFragmentManager = fragmentManager; } diff --git a/src/com/android/settings/accessibility/HearingAidUtils.java b/src/com/android/settings/accessibility/HearingAidUtils.java new file mode 100644 index 00000000000..a3d2c93f438 --- /dev/null +++ b/src/com/android/settings/accessibility/HearingAidUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + */ + +package com.android.settings.accessibility; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.bluetooth.HearingAidPairingDialogFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidProfile; + +/** Provides utility methods related hearing aids. */ +public final class HearingAidUtils { + private static final String TAG = "HearingAidUtils"; + + private HearingAidUtils(){} + + /** + * Launches pairing dialog when hearing aid device needs other side of hearing aid device to + * work. + * + * @param fragmentManager The {@link FragmentManager} used to show dialog fragment + * @param device The {@link CachedBluetoothDevice} need to be hearing aid device + */ + public static void launchHearingAidPairingDialog(FragmentManager fragmentManager, + @NonNull CachedBluetoothDevice device) { + if (device.isConnectedHearingAidDevice() + && device.getDeviceMode() == HearingAidProfile.DeviceMode.MODE_BINAURAL + && device.getSubDevice() == null) { + launchHearingAidPairingDialogInternal(fragmentManager, device); + } + } + + private static void launchHearingAidPairingDialogInternal(FragmentManager fragmentManager, + @NonNull CachedBluetoothDevice device) { + if (device.getDeviceSide() == HearingAidProfile.DeviceSide.SIDE_INVALID) { + Log.w(TAG, "Can not launch hearing aid pairing dialog for invalid side"); + return; + } + HearingAidPairingDialogFragment.newInstance(device).show(fragmentManager, + HearingAidPairingDialogFragment.TAG); + } +} diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java new file mode 100644 index 00000000000..ac48217732a --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + */ + +package com.android.settings.bluetooth; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +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; + +/** + * Provides a dialog to pair another side of hearing aid device. + */ +public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment { + public static final String TAG = "HearingAidPairingDialogFragment"; + private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side"; + + /** + * Creates a new {@link HearingAidPairingDialogFragment} and shows pair another side of hearing + * aid device according to {@code CachedBluetoothDevice} side. + * + * @param device The remote Bluetooth device, that needs to be hearing aid device. + * @return a DialogFragment + */ + public static HearingAidPairingDialogFragment newInstance(CachedBluetoothDevice device) { + Bundle args = new Bundle(1); + args.putInt(KEY_CACHED_DEVICE_SIDE, device.getDeviceSide()); + final HearingAidPairingDialogFragment fragment = new HearingAidPairingDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public int getMetricsCategory() { + // TODO(b/225117454): Need to update SettingsEnums later + return SettingsEnums.ACCESSIBILITY; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final int deviceSide = getArguments().getInt(KEY_CACHED_DEVICE_SIDE); + 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 + : R.string.bluetooth_pair_other_ear_dialog_right_ear_message; + final int pairBtnId = (deviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT) + ? R.string.bluetooth_pair_other_ear_dialog_right_ear_positive_button + : R.string.bluetooth_pair_other_ear_dialog_left_ear_positive_button; + + return new AlertDialog.Builder(getActivity()) + .setTitle(titleId) + .setMessage(messageId) + .setNegativeButton( + android.R.string.cancel, /* listener= */ null) + .setPositiveButton(pairBtnId, (dialog, which) -> positiveButtonListener()) + .create(); + } + + private void positiveButtonListener() { + new SubSettingLauncher(getActivity()) + .setDestination(BluetoothPairingDetail.class.getName()) + .setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY) + .launch(); + } +} diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java index ec122dfd506..6623b979066 100644 --- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java +++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java @@ -17,22 +17,26 @@ package com.android.settings.connecteddevice; import static com.android.settingslib.Utils.isAudioModeOngoingCall; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.accessibility.HearingAidUtils; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.BluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; @@ -54,6 +58,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle @VisibleForTesting LocalBluetoothManager mLocalBluetoothManager; private BluetoothDeviceUpdater mBluetoothDeviceUpdater; + private FragmentManager mFragmentManager; public AvailableMediaDeviceGroupController(Context context) { super(context, KEY); @@ -124,6 +129,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle } public void init(DashboardFragment fragment) { + mFragmentManager = fragment.getParentFragmentManager(); mBluetoothDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(fragment.getContext(), fragment, AvailableMediaDeviceGroupController.this); } @@ -138,6 +144,18 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle updateTitle(); } + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { + // exclude inactive device + if (activeDevice == null) { + return; + } + + if (bluetoothProfile == BluetoothProfile.HEARING_AID) { + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice); + } + } + private void updateTitle() { if (isAudioModeOngoingCall(mContext)) { // in phone call diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java index 1b8c8c3b5a7..affb28be97d 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,12 +33,16 @@ import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Intent; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import com.android.settings.R; 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.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; @@ -59,6 +64,7 @@ import org.robolectric.shadows.ShadowApplication; import java.util.ArrayList; import java.util.List; +/** Tests for {@link AccessibilityHearingAidPreferenceController}. */ @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) public class AccessibilityHearingAidPreferenceControllerTest { @@ -84,6 +90,8 @@ public class AccessibilityHearingAidPreferenceControllerTest { @Mock private LocalBluetoothManager mLocalBluetoothManager; @Mock + private BluetoothEventManager mEventManager; + @Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager; @Mock private HearingAidProfile mHearingAidProfile; @@ -221,6 +229,24 @@ public class AccessibilityHearingAidPreferenceControllerTest { assertThat(mPreferenceController.getConnectedHearingAidDevice()).isNull(); } + @Test + @Config(shadows = ShadowAlertDialogCompat.class) + public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() { + final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class); + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidProfile.DeviceSide.SIDE_LEFT); + mPreferenceController.setFragmentManager(mActivity.getSupportFragmentManager()); + + mPreferenceController.onActiveDeviceChanged(mCachedBluetoothDevice, + BluetoothProfile.HEARING_AID); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog.isShowing()).isTrue(); + } + private void setupBluetoothEnvironment() { ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); @@ -229,6 +255,7 @@ public class AccessibilityHearingAidPreferenceControllerTest { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); + doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); } private void setupHearingAidEnvironment() { @@ -239,7 +266,6 @@ public class AccessibilityHearingAidPreferenceControllerTest { mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME); - when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); } private void sendIntent(Intent intent) { diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 7c52754765f..891439ec05e 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -58,6 +58,7 @@ import com.android.settings.testutils.XmlTestUtils; import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.RestrictedPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexableRaw; import org.junit.Before; @@ -93,6 +94,7 @@ public class AccessibilitySettingsTest { private static final String DEFAULT_LABEL = "default label"; private static final Boolean SERVICE_ENABLED = true; private static final Boolean SERVICE_DISABLED = false; + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Spy @@ -114,6 +116,8 @@ public class AccessibilitySettingsTest { @Mock private AppOpsManager mAppOpsManager; + private Lifecycle mLifecycle; + @Before public void setup() { mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); @@ -127,6 +131,8 @@ public class AccessibilitySettingsTest { when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager); when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS), anyInt(), anyString())).thenReturn(AppOpsManager.MODE_ALLOWED); + mLifecycle = new Lifecycle(() -> mLifecycle); + when(mFragment.getSettingsLifecycle()).thenReturn(mLifecycle); } @Test diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java new file mode 100644 index 00000000000..5c440d2e7ce --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.SettingsActivity; +import com.android.settings.bluetooth.BluetoothPairingDetail; +import com.android.settings.bluetooth.HearingAidPairingDialogFragment; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidProfile; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** Tests for {@link HearingAidPairingDialogFragment}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class HearingAidPairingDialogFragmentTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side"; + + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + private FragmentActivity mActivity; + private HearingAidPairingDialogFragment mFragment; + + @Before + public void setUp() { + mFragment = spy(HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice)); + mActivity = Robolectric.setupActivity(FragmentActivity.class); + when(mFragment.getActivity()).thenReturn(mActivity); + } + + @Test + public void newInstance_deviceSideRight_argumentSideRight() { + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidProfile.DeviceSide.SIDE_RIGHT); + + mFragment = HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice); + + final Bundle bundle = mFragment.getArguments(); + assertThat(bundle.getInt(KEY_CACHED_DEVICE_SIDE)).isEqualTo( + HearingAidProfile.DeviceSide.SIDE_RIGHT); + } + + @Test + public void dialogPositiveButtonClick_intentToExpectedClass() { + final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY); + dialog.show(); + + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + + final Intent intent = shadowOf(mActivity).getNextStartedActivity(); + assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(BluetoothPairingDetail.class.getName()); + } + + @Test + public void dialogNegativeButtonClick_dismissDialog() { + final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY); + dialog.show(); + + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick(); + + assertThat(dialog.isShowing()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java new file mode 100644 index 00000000000..6918fb679e5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidProfile; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** Tests for {@link HearingAidUtils}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class HearingAidUtilsTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private CachedBluetoothDevice mSubCachedBluetoothDevice; + + private FragmentManager mFragmentManager; + + @Before + public void setUp() { + final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class); + mFragmentManager = mActivity.getSupportFragmentManager(); + } + + @After + public void tearDown() { + ShadowAlertDialogCompat.reset(); + } + @Test + public void launchHearingAidPairingDialog_deviceNotConnectedHearingAid_noDialog() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(false); + + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void launchHearingAidPairingDialog_deviceIsModeMonaural_noDialog() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_MONAURAL); + + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void launchHearingAidPairingDialog_deviceHasSubDevice_noDialog() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice); + + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void launchHearingAidPairingDialog_deviceIsInvalidSide_noDialog() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidProfile.DeviceSide.SIDE_INVALID); + + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void launchHearingAidPairingDialog_dialogShown() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidProfile.DeviceSide.SIDE_LEFT); + + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog.isShowing()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java index da1c4930d81..e1a3da6329a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java @@ -27,10 +27,13 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioManager; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; @@ -40,25 +43,31 @@ import com.android.settings.R; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; 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.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +/** Tests for {@link AvailableMediaDeviceGroupController}. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothUtils.class}) +@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class, + ShadowBluetoothUtils.class}) public class AvailableMediaDeviceGroupControllerTest { private static final String PREFERENCE_KEY_1 = "pref_key_1"; @@ -77,6 +86,8 @@ public class AvailableMediaDeviceGroupControllerTest { private BluetoothEventManager mEventManager; @Mock private LocalBluetoothManager mLocalManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; private PreferenceGroup mPreferenceGroup; private Context mContext; @@ -93,27 +104,26 @@ public class AvailableMediaDeviceGroupControllerTest { mPreference = new Preference(mContext); mPreference.setKey(PREFERENCE_KEY_1); mPreferenceGroup = spy(new PreferenceScreen(mContext, null)); + final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class); when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager); doReturn(mContext).when(mDashboardFragment).getContext(); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); + when(mDashboardFragment.getParentFragmentManager()).thenReturn( + mActivity.getSupportFragmentManager()); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mAudioManager = mContext.getSystemService(AudioManager.class); doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); - mAvailableMediaDeviceGroupController = new AvailableMediaDeviceGroupController(mContext); + mAvailableMediaDeviceGroupController = spy( + new AvailableMediaDeviceGroupController(mContext)); mAvailableMediaDeviceGroupController. setBluetoothDeviceUpdater(mAvailableMediaBluetoothDeviceUpdater); mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup; } - @After - public void tearDown() { - ShadowBluetoothUtils.reset(); - } - @Test public void onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() { mAvailableMediaDeviceGroupController.onDeviceAdded(mPreference); @@ -222,4 +232,21 @@ public class AvailableMediaDeviceGroupControllerTest { // Shouldn't crash mAvailableMediaDeviceGroupController.onStop(); } + + @Test + @Config(shadows = ShadowAlertDialogCompat.class) + public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() { + when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( + HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidProfile.DeviceSide.SIDE_LEFT); + mAvailableMediaDeviceGroupController.init(mDashboardFragment); + + mAvailableMediaDeviceGroupController.onActiveDeviceChanged(mCachedBluetoothDevice, + BluetoothProfile.HEARING_AID); + + final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog.isShowing()).isTrue(); + } }