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.settingslib.bluetooth; 18 19 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; 20 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL; 21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 22 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 23 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothClass; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHearingAid; 28 import android.bluetooth.BluetoothProfile; 29 import android.content.Context; 30 import android.util.Log; 31 32 import androidx.annotation.IntDef; 33 import androidx.annotation.NonNull; 34 35 import com.android.settingslib.R; 36 import com.android.settingslib.Utils; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 public class HearingAidProfile implements LocalBluetoothProfile { 44 @Retention(RetentionPolicy.SOURCE) 45 @IntDef({ 46 DeviceSide.SIDE_INVALID, 47 DeviceSide.SIDE_LEFT, 48 DeviceSide.SIDE_RIGHT 49 }) 50 51 /** Side definition for hearing aids. See {@link BluetoothHearingAid}. */ 52 public @interface DeviceSide { 53 int SIDE_INVALID = -1; 54 int SIDE_LEFT = 0; 55 int SIDE_RIGHT = 1; 56 } 57 58 @Retention(RetentionPolicy.SOURCE) 59 @IntDef({ 60 DeviceMode.MODE_INVALID, 61 DeviceMode.MODE_MONAURAL, 62 DeviceMode.MODE_BINAURAL 63 }) 64 65 /** Mode definition for hearing aids. See {@link BluetoothHearingAid}. */ 66 public @interface DeviceMode { 67 int MODE_INVALID = -1; 68 int MODE_MONAURAL = 0; 69 int MODE_BINAURAL = 1; 70 } 71 72 private static final String TAG = "HearingAidProfile"; 73 private static boolean V = true; 74 75 private Context mContext; 76 77 private BluetoothHearingAid mService; 78 private boolean mIsProfileReady; 79 80 private final CachedBluetoothDeviceManager mDeviceManager; 81 82 static final String NAME = "HearingAid"; 83 private final LocalBluetoothProfileManager mProfileManager; 84 private final BluetoothAdapter mBluetoothAdapter; 85 86 // Order of this profile in device profiles list 87 private static final int ORDINAL = 1; 88 89 // These callbacks run on the main thread. 90 private final class HearingAidServiceListener 91 implements BluetoothProfile.ServiceListener { 92 onServiceConnected(int profile, BluetoothProfile proxy)93 public void onServiceConnected(int profile, BluetoothProfile proxy) { 94 mService = (BluetoothHearingAid) proxy; 95 // We just bound to the service, so refresh the UI for any connected HearingAid devices. 96 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 97 while (!deviceList.isEmpty()) { 98 BluetoothDevice nextDevice = deviceList.remove(0); 99 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 100 // we may add a new device here, but generally this should not happen 101 if (device == null) { 102 if (V) { 103 Log.d(TAG, "HearingAidProfile found new device: " + nextDevice); 104 } 105 device = mDeviceManager.addDevice(nextDevice); 106 } 107 device.onProfileStateChanged(HearingAidProfile.this, 108 BluetoothProfile.STATE_CONNECTED); 109 device.refresh(); 110 } 111 112 // Check current list of CachedDevices to see if any are Hearing Aid devices. 113 mDeviceManager.updateHearingAidsDevices(); 114 mIsProfileReady = true; 115 mProfileManager.callServiceConnectedListeners(); 116 } 117 onServiceDisconnected(int profile)118 public void onServiceDisconnected(int profile) { 119 mIsProfileReady = false; 120 mProfileManager.callServiceDisconnectedListeners(); 121 } 122 } 123 isProfileReady()124 public boolean isProfileReady() { 125 return mIsProfileReady; 126 } 127 128 @Override getProfileId()129 public int getProfileId() { 130 return BluetoothProfile.HEARING_AID; 131 } 132 HearingAidProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)133 HearingAidProfile(Context context, CachedBluetoothDeviceManager deviceManager, 134 LocalBluetoothProfileManager profileManager) { 135 mContext = context; 136 mDeviceManager = deviceManager; 137 mProfileManager = profileManager; 138 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 139 mBluetoothAdapter.getProfileProxy(context, 140 new HearingAidServiceListener(), BluetoothProfile.HEARING_AID); 141 } 142 accessProfileEnabled()143 public boolean accessProfileEnabled() { 144 return false; 145 } 146 isAutoConnectable()147 public boolean isAutoConnectable() { 148 return true; 149 } 150 151 /** 152 * Get Hearing Aid devices matching connection states{ 153 * @code BluetoothProfile.STATE_CONNECTED, 154 * @code BluetoothProfile.STATE_CONNECTING, 155 * @code BluetoothProfile.STATE_DISCONNECTING} 156 * 157 * @return Matching device list 158 */ getConnectedDevices()159 public List<BluetoothDevice> getConnectedDevices() { 160 return getDevicesByStates(new int[] { 161 BluetoothProfile.STATE_CONNECTED, 162 BluetoothProfile.STATE_CONNECTING, 163 BluetoothProfile.STATE_DISCONNECTING}); 164 } 165 166 /** 167 * Get Hearing Aid devices matching connection states{ 168 * @code BluetoothProfile.STATE_DISCONNECTED, 169 * @code BluetoothProfile.STATE_CONNECTED, 170 * @code BluetoothProfile.STATE_CONNECTING, 171 * @code BluetoothProfile.STATE_DISCONNECTING} 172 * 173 * @return Matching device list 174 */ getConnectableDevices()175 public List<BluetoothDevice> getConnectableDevices() { 176 return getDevicesByStates(new int[] { 177 BluetoothProfile.STATE_DISCONNECTED, 178 BluetoothProfile.STATE_CONNECTED, 179 BluetoothProfile.STATE_CONNECTING, 180 BluetoothProfile.STATE_DISCONNECTING}); 181 } 182 getDevicesByStates(int[] states)183 private List<BluetoothDevice> getDevicesByStates(int[] states) { 184 if (mService == null) { 185 return new ArrayList<BluetoothDevice>(0); 186 } 187 return mService.getDevicesMatchingConnectionStates(states); 188 } 189 getConnectionStatus(BluetoothDevice device)190 public int getConnectionStatus(BluetoothDevice device) { 191 if (mService == null) { 192 return BluetoothProfile.STATE_DISCONNECTED; 193 } 194 return mService.getConnectionState(device); 195 } 196 setActiveDevice(BluetoothDevice device)197 public boolean setActiveDevice(BluetoothDevice device) { 198 if (mBluetoothAdapter == null) { 199 return false; 200 } 201 int profiles = Utils.isAudioModeOngoingCall(mContext) 202 ? ACTIVE_DEVICE_PHONE_CALL 203 : ACTIVE_DEVICE_AUDIO; 204 return device == null 205 ? mBluetoothAdapter.removeActiveDevice(profiles) 206 : mBluetoothAdapter.setActiveDevice(device, profiles); 207 } 208 getActiveDevices()209 public List<BluetoothDevice> getActiveDevices() { 210 if (mBluetoothAdapter == null) { 211 return new ArrayList<>(); 212 } 213 return mBluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID); 214 } 215 216 @Override isEnabled(BluetoothDevice device)217 public boolean isEnabled(BluetoothDevice device) { 218 if (mService == null || device == null) { 219 return false; 220 } 221 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 222 } 223 224 @Override getConnectionPolicy(BluetoothDevice device)225 public int getConnectionPolicy(BluetoothDevice device) { 226 if (mService == null || device == null) { 227 return CONNECTION_POLICY_FORBIDDEN; 228 } 229 return mService.getConnectionPolicy(device); 230 } 231 232 @Override setEnabled(BluetoothDevice device, boolean enabled)233 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 234 boolean isEnabled = false; 235 if (mService == null || device == null) { 236 return false; 237 } 238 if (enabled) { 239 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 240 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 241 } 242 } else { 243 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 244 } 245 246 return isEnabled; 247 } 248 249 /** 250 * Tells remote device to set an absolute volume. 251 * 252 * @param volume Absolute volume to be set on remote 253 */ setVolume(int volume)254 public void setVolume(int volume) { 255 if (mService == null) { 256 return; 257 } 258 mService.setVolume(volume); 259 } 260 261 /** 262 * Gets the HiSyncId (unique hearing aid device identifier) of the device. 263 * 264 * @param device Bluetooth device 265 * @return the HiSyncId of the device 266 */ getHiSyncId(BluetoothDevice device)267 public long getHiSyncId(BluetoothDevice device) { 268 if (mService == null || device == null) { 269 return BluetoothHearingAid.HI_SYNC_ID_INVALID; 270 } 271 return mService.getHiSyncId(device); 272 } 273 274 /** 275 * Gets the side of the device. 276 * 277 * @param device Bluetooth device. 278 * @return side of the device. See {@link DeviceSide}. 279 */ 280 @DeviceSide getDeviceSide(@onNull BluetoothDevice device)281 public int getDeviceSide(@NonNull BluetoothDevice device) { 282 final int defaultValue = DeviceSide.SIDE_INVALID; 283 if (mService == null) { 284 Log.w(TAG, "Proxy not attached to HearingAidService"); 285 return defaultValue; 286 } 287 288 return mService.getDeviceSide(device); 289 } 290 291 /** 292 * Gets the mode of the device. 293 * 294 * @param device Bluetooth device 295 * @return mode of the device. See {@link DeviceMode}. 296 */ 297 @DeviceMode getDeviceMode(@onNull BluetoothDevice device)298 public int getDeviceMode(@NonNull BluetoothDevice device) { 299 final int defaultValue = DeviceMode.MODE_INVALID; 300 if (mService == null) { 301 Log.w(TAG, "Proxy not attached to HearingAidService"); 302 return defaultValue; 303 } 304 305 return mService.getDeviceMode(device); 306 } 307 toString()308 public String toString() { 309 return NAME; 310 } 311 getOrdinal()312 public int getOrdinal() { 313 return ORDINAL; 314 } 315 getNameResource(BluetoothDevice device)316 public int getNameResource(BluetoothDevice device) { 317 return R.string.bluetooth_profile_hearing_aid; 318 } 319 getSummaryResourceForDevice(BluetoothDevice device)320 public int getSummaryResourceForDevice(BluetoothDevice device) { 321 int state = getConnectionStatus(device); 322 switch (state) { 323 case BluetoothProfile.STATE_DISCONNECTED: 324 return R.string.bluetooth_hearing_aid_profile_summary_use_for; 325 326 case BluetoothProfile.STATE_CONNECTED: 327 return R.string.bluetooth_hearing_aid_profile_summary_connected; 328 329 default: 330 return BluetoothUtils.getConnectionStateSummary(state); 331 } 332 } 333 getDrawableResource(BluetoothClass btClass)334 public int getDrawableResource(BluetoothClass btClass) { 335 return com.android.internal.R.drawable.ic_bt_hearing_aid; 336 } 337 finalize()338 protected void finalize() { 339 Log.d(TAG, "finalize()"); 340 if (mService != null) { 341 try { 342 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEARING_AID, 343 mService); 344 mService = null; 345 }catch (Throwable t) { 346 Log.w(TAG, "Error cleaning up Hearing Aid proxy", t); 347 } 348 } 349 } 350 } 351