• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.accessibility;
18 
19 import android.app.settings.SettingsEnums;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHearingAid;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.os.Bundle;
29 import android.text.TextUtils;
30 import android.util.Log;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.fragment.app.FragmentManager;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.settings.R;
38 import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.core.SubSettingLauncher;
41 import com.android.settingslib.bluetooth.BluetoothCallback;
42 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
43 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
44 import com.android.settingslib.bluetooth.HearingAidProfile;
45 import com.android.settingslib.bluetooth.LocalBluetoothManager;
46 import com.android.settingslib.core.lifecycle.LifecycleObserver;
47 import com.android.settingslib.core.lifecycle.events.OnStart;
48 import com.android.settingslib.core.lifecycle.events.OnStop;
49 
50 import java.util.List;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.FutureTask;
53 
54 /**
55  * Controller that shows and updates the bluetooth device name
56  */
57 public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
58         implements LifecycleObserver, OnStart, OnStop, BluetoothCallback {
59     private static final String TAG = "AccessibilityHearingAidPreferenceController";
60     private Preference mHearingAidPreference;
61 
62     private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
63         @Override
64         public void onReceive(Context context, Intent intent) {
65             if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
66                 final int state = intent.getIntExtra(BluetoothHearingAid.EXTRA_STATE,
67                         BluetoothHearingAid.STATE_DISCONNECTED);
68                 if (state == BluetoothHearingAid.STATE_CONNECTED) {
69                     updateState(mHearingAidPreference);
70                 } else {
71                     mHearingAidPreference
72                             .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
73                 }
74             } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
75                 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
76                         BluetoothAdapter.ERROR);
77                 if (state != BluetoothAdapter.STATE_ON) {
78                     mHearingAidPreference
79                             .setSummary(R.string.accessibility_hearingaid_not_connected_summary);
80                 }
81             }
82         }
83     };
84 
85     private final LocalBluetoothManager mLocalBluetoothManager;
86     private final BluetoothAdapter mBluetoothAdapter;
87 
88     private FragmentManager mFragmentManager;
89 
AccessibilityHearingAidPreferenceController(Context context, String preferenceKey)90     public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
91         super(context, preferenceKey);
92         mLocalBluetoothManager = getLocalBluetoothManager();
93         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
94     }
95 
96     @Override
displayPreference(PreferenceScreen screen)97     public void displayPreference(PreferenceScreen screen) {
98         super.displayPreference(screen);
99         mHearingAidPreference = screen.findPreference(getPreferenceKey());
100     }
101 
102     @Override
getAvailabilityStatus()103     public int getAvailabilityStatus() {
104         return isHearingAidProfileSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
105     }
106 
107     @Override
onStart()108     public void onStart() {
109         IntentFilter filter = new IntentFilter();
110         filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
111         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
112         mContext.registerReceiver(mHearingAidChangedReceiver, filter);
113         mLocalBluetoothManager.getEventManager().registerCallback(this);
114     }
115 
116     @Override
onStop()117     public void onStop() {
118         mContext.unregisterReceiver(mHearingAidChangedReceiver);
119         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
120     }
121 
122     @Override
handlePreferenceTreeClick(Preference preference)123     public boolean handlePreferenceTreeClick(Preference preference) {
124         if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
125             final CachedBluetoothDevice device = getConnectedHearingAidDevice();
126             if (device == null) {
127                 launchHearingAidInstructionDialog();
128             } else {
129                 launchBluetoothDeviceDetailSetting(device);
130             }
131             return true;
132         }
133         return false;
134     }
135 
136     @Override
getSummary()137     public CharSequence getSummary() {
138         final CachedBluetoothDevice device = getConnectedHearingAidDevice();
139         if (device == null) {
140             return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
141         }
142 
143         final int connectedNum = getConnectedHearingAidDeviceNum();
144         final CharSequence name = device.getName();
145         final int side = device.getDeviceSide();
146         final CachedBluetoothDevice subDevice = device.getSubDevice();
147         if (connectedNum > 1) {
148             return mContext.getString(R.string.accessibility_hearingaid_more_device_summary, name);
149         }
150         if (subDevice != null && subDevice.isConnected()) {
151             return mContext.getString(
152                     R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
153         }
154         if (side == HearingAidProfile.DeviceSide.SIDE_INVALID) {
155             return mContext.getString(
156                     R.string.accessibility_hearingaid_active_device_summary, name);
157         }
158         return (side == HearingAidProfile.DeviceSide.SIDE_LEFT)
159                 ? mContext.getString(
160                         R.string.accessibility_hearingaid_left_side_device_summary, name)
161                 : mContext.getString(
162                         R.string.accessibility_hearingaid_right_side_device_summary, name);
163     }
164 
165     @Override
onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)166     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
167         if (activeDevice == null) {
168             return;
169         }
170 
171         if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
172             HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice);
173         }
174     }
175 
setFragmentManager(FragmentManager fragmentManager)176     public void setFragmentManager(FragmentManager fragmentManager) {
177         mFragmentManager = fragmentManager;
178     }
179 
180     @VisibleForTesting
getConnectedHearingAidDevice()181     CachedBluetoothDevice getConnectedHearingAidDevice() {
182         if (!isHearingAidProfileSupported()) {
183             return null;
184         }
185 
186         final CachedBluetoothDeviceManager deviceManager =
187                 mLocalBluetoothManager.getCachedDeviceManager();
188         final HearingAidProfile hearingAidProfile =
189                 mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
190         final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
191         for (BluetoothDevice obj : deviceList) {
192             if (!deviceManager.isSubDevice(obj)) {
193                 return deviceManager.findDevice(obj);
194             }
195         }
196         return null;
197     }
198 
getConnectedHearingAidDeviceNum()199     private int getConnectedHearingAidDeviceNum() {
200         if (!isHearingAidProfileSupported()) {
201             return 0;
202         }
203 
204         final CachedBluetoothDeviceManager deviceManager =
205                 mLocalBluetoothManager.getCachedDeviceManager();
206         final HearingAidProfile hearingAidProfile =
207                 mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
208         final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
209         return (int) deviceList.stream()
210                 .filter(device -> !deviceManager.isSubDevice(device))
211                 .count();
212     }
213 
isHearingAidProfileSupported()214     private boolean isHearingAidProfileSupported() {
215         if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
216             return false;
217         }
218         final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
219         return supportedList.contains(BluetoothProfile.HEARING_AID);
220     }
221 
getLocalBluetoothManager()222     private LocalBluetoothManager getLocalBluetoothManager() {
223         final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>(
224                 // Avoid StrictMode ThreadPolicy violation
225                 () -> com.android.settings.bluetooth.Utils.getLocalBtManager(mContext));
226         try {
227             localBtManagerFutureTask.run();
228             return localBtManagerFutureTask.get();
229         } catch (InterruptedException | ExecutionException e) {
230             Log.w(TAG, "Error getting LocalBluetoothManager.", e);
231             return null;
232         }
233     }
234 
235     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setPreference(Preference preference)236     void setPreference(Preference preference) {
237         mHearingAidPreference = preference;
238     }
239 
240     @VisibleForTesting
launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device)241     void launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device) {
242         if (device == null) {
243             return;
244         }
245         final Bundle args = new Bundle();
246         args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
247                 device.getDevice().getAddress());
248 
249         new SubSettingLauncher(mContext)
250                 .setDestination(BluetoothDeviceDetailsFragment.class.getName())
251                 .setArguments(args)
252                 .setTitleRes(R.string.device_details_title)
253                 .setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
254                 .launch();
255     }
256 
257     @VisibleForTesting
launchHearingAidInstructionDialog()258     void launchHearingAidInstructionDialog() {
259         HearingAidDialogFragment fragment = HearingAidDialogFragment.newInstance();
260         fragment.show(mFragmentManager, HearingAidDialogFragment.class.toString());
261     }
262 }