Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit acd3f94f authored by jasonwshsu's avatar jasonwshsu Committed by Menghan Li
Browse files

Pop up pairing another ear dialog when detecting hearing aid is a set

* Pop up dialog in 'Connected deivce' page and 'Accessibility -> Hearing
  aids' page

Bug: 225117454
Bug: 226511985
Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidPairingDialogFragmentTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AvailableMediaDeviceGroupControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AccessibilityHearingAidPreferenceControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidUtils

Change-Id: I34a1e3ac680a7efe97dc501bfbe93f840ad16364
parent ee56c24a
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -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;
    }
+59 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+88 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+18 −0
Original line number Diff line number Diff line
@@ -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
+27 −1
Original line number Diff line number Diff line
@@ -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) {
Loading