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 }