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

Commit 53a12ee7 authored by timhypeng's avatar timhypeng
Browse files

Add Hearing Aid UI into Settings-Accessibility App

- dynamically show/hide preference by HearingAid profile is supported or not
- add AccessibilityHearingAidPreferenceController to handle hearingAid preference
- add HearingAidDialogFragment to handle dialog behavior

Bug: 109948484
Test: make -j50 RunSettingsRoboTests

Change-Id: Ic55dde475dc40311f7e652f4a86d342597f09f0e
parent 64771b53
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4574,6 +4574,8 @@
    <!-- Used in the Captions settings screen to control turning on/off the feature entirely -->
    <string name="accessibility_caption_master_switch_title">Use captions</string>
    <!-- Button text for the accessibility dialog continue to the next screen for hearing aid. [CHAR LIMIT=32] -->
    <string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
    <!-- Title for the accessibility preference for hearing aid. [CHAR LIMIT=35] -->
    <string name="accessibility_hearingaid_title">Hearing aids</string>
    <!-- Summary for the accessibility preference for hearing aid when not connected. [CHAR LIMIT=50] -->
+5 −0
Original line number Diff line number Diff line
@@ -113,6 +113,11 @@
                android:summary="@string/accessibility_toggle_master_mono_summary"
                android:persistent="false"/>

        <Preference
            android:key="hearing_aid_preference"
            android:summary="@string/accessibility_hearingaid_not_connected_summary"
            android:title="@string/accessibility_hearingaid_title"/>

        <Preference
                android:fragment="com.android.settings.accessibility.CaptionPropertiesFragment"
                android:key="captioning_preference_screen"
+217 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.internal.logging.nano.MetricsProto;
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.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Controller that shows and updates the bluetooth device name
 */
public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
        implements LifecycleObserver, OnResume, OnPause {
    private static final String TAG = "AccessibilityHearingAidPreferenceController";
    private Preference mHearingAidPreference;

    private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
                final int state = intent.getIntExtra(BluetoothHearingAid.EXTRA_STATE,
                        BluetoothHearingAid.STATE_DISCONNECTED);
                if (state == BluetoothHearingAid.STATE_CONNECTED) {
                    updateState(mHearingAidPreference);
                } else {
                    mHearingAidPreference
                            .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
                }
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR);
                if (state != BluetoothAdapter.STATE_ON) {
                    mHearingAidPreference
                            .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
                }
            }
        }
    };

    private final LocalBluetoothManager mLocalBluetoothManager;
    //cache value of supporting hearing aid or not
    private boolean mHearingAidProfileSupported;
    private FragmentManager mFragmentManager;

    public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mLocalBluetoothManager = getLocalBluetoothManager();
        mHearingAidProfileSupported = isHearingAidProfileSupported();
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mHearingAidPreference = screen.findPreference(getPreferenceKey());
    }

    @Override
    public int getAvailabilityStatus() {
        return mHearingAidProfileSupported ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public void onResume() {
        if (mHearingAidProfileSupported) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            mContext.registerReceiver(mHearingAidChangedReceiver, filter);
        }
    }

    @Override
    public void onPause() {
        if (mHearingAidProfileSupported) {
            mContext.unregisterReceiver(mHearingAidChangedReceiver);
        }
    }

    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (TextUtils.equals(preference.getKey(), getPreferenceKey())){
            final CachedBluetoothDevice device = getConnectedHearingAidDevice();
            if (device == null) {
                launchHearingAidInstructionDialog();
            } else {
                launchBluetoothDeviceDetailSetting(device);
            }
            return true;
        }
        return false;
    }

    @Override
    public CharSequence getSummary() {
        final CachedBluetoothDevice device = getConnectedHearingAidDevice();
        if (device == null) {
            return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
        }
        return device.getName();
    }

    public void setFragmentManager(FragmentManager fragmentManager) {
        mFragmentManager = fragmentManager;
    }

    private CachedBluetoothDevice getConnectedHearingAidDevice() {
        if (!mHearingAidProfileSupported) {
            return null;
        }
        final LocalBluetoothAdapter localAdapter = mLocalBluetoothManager.getBluetoothAdapter();
        if (!localAdapter.isEnabled()) {
            return null;
        }
        final List<BluetoothDevice> deviceList = mLocalBluetoothManager.getProfileManager()
                .getHearingAidProfile().getConnectedDevices();
        final Iterator it = deviceList.iterator();
        if (it.hasNext()) {
            BluetoothDevice obj = (BluetoothDevice)it.next();
            return mLocalBluetoothManager.getCachedDeviceManager().findDevice(obj);
        }
        return null;
    }

    private boolean isHearingAidProfileSupported() {
        final LocalBluetoothAdapter localAdapter = mLocalBluetoothManager.getBluetoothAdapter();
        final List<Integer> supportedList = localAdapter.getSupportedProfiles();
        if (supportedList.contains(BluetoothProfile.HEARING_AID)) {
            return true;
        }
        return false;
    }

    private LocalBluetoothManager getLocalBluetoothManager() {
        final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>(
                // Avoid StrictMode ThreadPolicy violation
                () -> com.android.settings.bluetooth.Utils.getLocalBtManager(mContext));
        try {
            localBtManagerFutureTask.run();
            return localBtManagerFutureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            Log.w(TAG, "Error getting LocalBluetoothManager.", e);
            return null;
        }
    }

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    void setPreference(Preference preference) {
        mHearingAidPreference = preference;
    }

    @VisibleForTesting
    void launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device) {
        if (device == null) {
            return;
        }
        final Bundle args = new Bundle();
        args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
                device.getDevice().getAddress());

        new SubSettingLauncher(mContext)
                .setDestination(BluetoothDeviceDetailsFragment.class.getName())
                .setArguments(args)
                .setTitleRes(R.string.device_details_title)
                .setSourceMetricsCategory(MetricsProto.MetricsEvent.ACCESSIBILITY)
                .launch();
    }

    @VisibleForTesting
    void launchHearingAidInstructionDialog() {
        HearingAidDialogFragment fragment = HearingAidDialogFragment.newInstance();
        fragment.show(mFragmentManager, HearingAidDialogFragment.class.toString());
    }
}
 No newline at end of file
+21 −0
Original line number Diff line number Diff line
@@ -110,6 +110,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
            "select_long_press_timeout_preference";
    private static final String ACCESSIBILITY_SHORTCUT_PREFERENCE =
            "accessibility_shortcut_preference";
    private static final String HEARING_AID_PREFERENCE =
            "hearing_aid_preference";
    private static final String CAPTIONING_PREFERENCE_SCREEN =
            "captioning_preference_screen";
    private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
@@ -221,9 +223,11 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
    private Preference mAutoclickPreferenceScreen;
    private Preference mAccessibilityShortcutPreferenceScreen;
    private Preference mDisplayDaltonizerPreferenceScreen;
    private Preference mHearingAidPreference;
    private Preference mVibrationPreferenceScreen;
    private SwitchPreference mToggleInversionPreference;
    private ColorInversionPreferenceController mInversionPreferenceController;
    private AccessibilityHearingAidPreferenceController mHearingAidPreferenceController;

    private int mLongPressTimeoutDefault;

@@ -275,6 +279,15 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
                .getSystemService(Context.DEVICE_POLICY_SERVICE));
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mHearingAidPreferenceController = new AccessibilityHearingAidPreferenceController
                (context, HEARING_AID_PREFERENCE);
        mHearingAidPreferenceController.setFragmentManager(getFragmentManager());
        getLifecycle().addObserver(mHearingAidPreferenceController);
    }

    @Override
    public void onResume() {
        super.onResume();
@@ -335,6 +348,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
        } else if (mToggleMasterMonoPreference == preference) {
            handleToggleMasterMonoPreferenceClick();
            return true;
        } else if (mHearingAidPreferenceController.handlePreferenceTreeClick(preference)) {
            return true;
        }
        return super.onPreferenceTreeClick(preference);
    }
@@ -452,6 +467,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
            }
        }

        // Hearing Aid.
        mHearingAidPreference = findPreference(HEARING_AID_PREFERENCE);
        mHearingAidPreferenceController.displayPreference(getPreferenceScreen());

        // Captioning.
        mCaptioningPreferenceScreen = findPreference(CAPTIONING_PREFERENCE_SCREEN);

@@ -686,6 +705,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements

        updateVibrationSummary(mVibrationPreferenceScreen);

        mHearingAidPreferenceController.updateState(mHearingAidPreference);

        updateFeatureSummary(Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
                mCaptioningPreferenceScreen);
        updateFeatureSummary(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;

import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;

public class HearingAidDialogFragment extends InstrumentedDialogFragment {
    public static HearingAidDialogFragment newInstance() {
        HearingAidDialogFragment frag = new HearingAidDialogFragment();
        return frag;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.accessibility_hearingaid_pair_instructions_first_message)
                .setMessage(R.string.accessibility_hearingaid_pair_instructions_second_message)
                .setPositiveButton(R.string.accessibility_hearingaid_instruction_continue_button,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                launchBluetoothAddDeviceSetting();
                            }
                        })
                .setNegativeButton(android.R.string.cancel,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) { }
                        })
                .create();
    }

    @Override
    public int getMetricsCategory() {
        return MetricsProto.MetricsEvent.DIALOG_ACCESSIBILITY_HEARINGAID;
    }

    private void launchBluetoothAddDeviceSetting() {
        new SubSettingLauncher(getActivity())
                .setDestination(BluetoothPairingDetail.class.getName())
                .setSourceMetricsCategory(MetricsProto.MetricsEvent.ACCESSIBILITY)
                .launch();
    }
}
Loading