1 /* 2 * Copyright (C) 2022 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.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothClass; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHapClient; 28 import android.bluetooth.BluetoothManager; 29 import android.bluetooth.BluetoothProfile; 30 import android.content.Context; 31 import android.util.Log; 32 33 import com.android.settingslib.R; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * HapClientProfile handles the Bluetooth HAP service client role. 42 */ 43 public class HapClientProfile implements LocalBluetoothProfile { 44 @Retention(RetentionPolicy.SOURCE) 45 @IntDef(flag = true, value = { 46 HearingAidType.TYPE_INVALID, 47 HearingAidType.TYPE_BINAURAL, 48 HearingAidType.TYPE_MONAURAL, 49 HearingAidType.TYPE_BANDED, 50 HearingAidType.TYPE_RFU 51 }) 52 53 /** Hearing aid type definition for HAP Client. */ 54 public @interface HearingAidType { 55 int TYPE_INVALID = -1; 56 int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL; 57 int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL; 58 int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED; 59 int TYPE_RFU = BluetoothHapClient.TYPE_RFU; 60 } 61 62 static final String NAME = "HapClient"; 63 private static final String TAG = "HapClientProfile"; 64 65 // Order of this profile in device profiles list 66 private static final int ORDINAL = 1; 67 68 private final BluetoothAdapter mBluetoothAdapter; 69 private final CachedBluetoothDeviceManager mDeviceManager; 70 private final LocalBluetoothProfileManager mProfileManager; 71 private BluetoothHapClient mService; 72 private boolean mIsProfileReady; 73 74 // These callbacks run on the main thread. 75 private final class HapClientServiceListener implements BluetoothProfile.ServiceListener { 76 77 @Override onServiceConnected(int profile, BluetoothProfile proxy)78 public void onServiceConnected(int profile, BluetoothProfile proxy) { 79 mService = (BluetoothHapClient) proxy; 80 // We just bound to the service, so refresh the UI for any connected HapClient devices. 81 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 82 while (!deviceList.isEmpty()) { 83 BluetoothDevice nextDevice = deviceList.remove(0); 84 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 85 // Adds a new device into mDeviceManager if it does not exist 86 if (device == null) { 87 Log.w(TAG, "HapClient profile found new device: " + nextDevice); 88 device = mDeviceManager.addDevice(nextDevice); 89 } 90 device.onProfileStateChanged( 91 HapClientProfile.this, BluetoothProfile.STATE_CONNECTED); 92 device.refresh(); 93 } 94 95 mIsProfileReady = true; 96 mProfileManager.callServiceConnectedListeners(); 97 } 98 99 @Override onServiceDisconnected(int profile)100 public void onServiceDisconnected(int profile) { 101 mIsProfileReady = false; 102 mProfileManager.callServiceDisconnectedListeners(); 103 } 104 } 105 HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)106 HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager, 107 LocalBluetoothProfileManager profileManager) { 108 mDeviceManager = deviceManager; 109 mProfileManager = profileManager; 110 BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); 111 if (bluetoothManager != null) { 112 mBluetoothAdapter = bluetoothManager.getAdapter(); 113 mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(), 114 BluetoothProfile.HAP_CLIENT); 115 } else { 116 mBluetoothAdapter = null; 117 } 118 } 119 120 /** 121 * Get hearing aid devices matching connection states{ 122 * {@code BluetoothProfile.STATE_CONNECTED}, 123 * {@code BluetoothProfile.STATE_CONNECTING}, 124 * {@code BluetoothProfile.STATE_DISCONNECTING}} 125 * 126 * @return Matching device list 127 */ getConnectedDevices()128 public List<BluetoothDevice> getConnectedDevices() { 129 return getDevicesByStates(new int[] { 130 BluetoothProfile.STATE_CONNECTED, 131 BluetoothProfile.STATE_CONNECTING, 132 BluetoothProfile.STATE_DISCONNECTING}); 133 } 134 135 /** 136 * Get hearing aid devices matching connection states{ 137 * {@code BluetoothProfile.STATE_DISCONNECTED}, 138 * {@code BluetoothProfile.STATE_CONNECTED}, 139 * {@code BluetoothProfile.STATE_CONNECTING}, 140 * {@code BluetoothProfile.STATE_DISCONNECTING}} 141 * 142 * @return Matching device list 143 */ getConnectableDevices()144 public List<BluetoothDevice> getConnectableDevices() { 145 return getDevicesByStates(new int[] { 146 BluetoothProfile.STATE_DISCONNECTED, 147 BluetoothProfile.STATE_CONNECTED, 148 BluetoothProfile.STATE_CONNECTING, 149 BluetoothProfile.STATE_DISCONNECTING}); 150 } 151 getDevicesByStates(int[] states)152 private List<BluetoothDevice> getDevicesByStates(int[] states) { 153 if (mService == null) { 154 return new ArrayList<>(0); 155 } 156 return mService.getDevicesMatchingConnectionStates(states); 157 } 158 159 /** 160 * Gets the hearing aid type of the device. 161 * 162 * @param device is the device for which we want to get the hearing aid type 163 * @return hearing aid type 164 */ 165 @HearingAidType getHearingAidType(@onNull BluetoothDevice device)166 public int getHearingAidType(@NonNull BluetoothDevice device) { 167 if (mService == null) { 168 return HearingAidType.TYPE_INVALID; 169 } 170 return mService.getHearingAidType(device); 171 } 172 173 /** 174 * Gets if this device supports synchronized presets or not 175 * 176 * @param device is the device for which we want to know if supports synchronized presets 177 * @return {@code true} if the device supports synchronized presets 178 */ supportsSynchronizedPresets(@onNull BluetoothDevice device)179 public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) { 180 if (mService == null) { 181 return false; 182 } 183 return mService.supportsSynchronizedPresets(device); 184 } 185 186 /** 187 * Gets if this device supports independent presets or not 188 * 189 * @param device is the device for which we want to know if supports independent presets 190 * @return {@code true} if the device supports independent presets 191 */ supportsIndependentPresets(@onNull BluetoothDevice device)192 public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) { 193 if (mService == null) { 194 return false; 195 } 196 return mService.supportsIndependentPresets(device); 197 } 198 199 /** 200 * Gets if this device supports dynamic presets or not 201 * 202 * @param device is the device for which we want to know if supports dynamic presets 203 * @return {@code true} if the device supports dynamic presets 204 */ supportsDynamicPresets(@onNull BluetoothDevice device)205 public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) { 206 if (mService == null) { 207 return false; 208 } 209 return mService.supportsDynamicPresets(device); 210 } 211 212 /** 213 * Gets if this device supports writable presets or not 214 * 215 * @param device is the device for which we want to know if supports writable presets 216 * @return {@code true} if the device supports writable presets 217 */ supportsWritablePresets(@onNull BluetoothDevice device)218 public boolean supportsWritablePresets(@NonNull BluetoothDevice device) { 219 if (mService == null) { 220 return false; 221 } 222 return mService.supportsWritablePresets(device); 223 } 224 225 @Override accessProfileEnabled()226 public boolean accessProfileEnabled() { 227 return false; 228 } 229 230 @Override isAutoConnectable()231 public boolean isAutoConnectable() { 232 return true; 233 } 234 235 @Override getConnectionStatus(BluetoothDevice device)236 public int getConnectionStatus(BluetoothDevice device) { 237 if (mService == null) { 238 return BluetoothProfile.STATE_DISCONNECTED; 239 } 240 return mService.getConnectionState(device); 241 } 242 243 @Override isEnabled(BluetoothDevice device)244 public boolean isEnabled(BluetoothDevice device) { 245 if (mService == null || device == null) { 246 return false; 247 } 248 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 249 } 250 251 @Override getConnectionPolicy(BluetoothDevice device)252 public int getConnectionPolicy(BluetoothDevice device) { 253 if (mService == null || device == null) { 254 return CONNECTION_POLICY_FORBIDDEN; 255 } 256 return mService.getConnectionPolicy(device); 257 } 258 259 @Override setEnabled(BluetoothDevice device, boolean enabled)260 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 261 boolean isEnabled = false; 262 if (mService == null || device == null) { 263 return false; 264 } 265 if (enabled) { 266 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 267 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 268 } 269 } else { 270 isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 271 } 272 273 return isEnabled; 274 } 275 276 @Override isProfileReady()277 public boolean isProfileReady() { 278 return mIsProfileReady; 279 } 280 281 @Override getProfileId()282 public int getProfileId() { 283 return BluetoothProfile.HAP_CLIENT; 284 } 285 286 @Override getOrdinal()287 public int getOrdinal() { 288 return ORDINAL; 289 } 290 291 @Override getNameResource(BluetoothDevice device)292 public int getNameResource(BluetoothDevice device) { 293 return R.string.bluetooth_profile_hearing_aid; 294 } 295 296 @Override getSummaryResourceForDevice(BluetoothDevice device)297 public int getSummaryResourceForDevice(BluetoothDevice device) { 298 int state = getConnectionStatus(device); 299 switch (state) { 300 case BluetoothProfile.STATE_DISCONNECTED: 301 return R.string.bluetooth_hearing_aid_profile_summary_use_for; 302 303 case BluetoothProfile.STATE_CONNECTED: 304 return R.string.bluetooth_hearing_aid_profile_summary_connected; 305 306 default: 307 return BluetoothUtils.getConnectionStateSummary(state); 308 } 309 } 310 311 @Override getDrawableResource(BluetoothClass btClass)312 public int getDrawableResource(BluetoothClass btClass) { 313 return com.android.internal.R.drawable.ic_bt_hearing_aid; 314 } 315 316 /** 317 * Gets the name of this class 318 * 319 * @return the name of this class 320 */ toString()321 public String toString() { 322 return NAME; 323 } 324 finalize()325 protected void finalize() { 326 Log.d(TAG, "finalize()"); 327 if (mService != null) { 328 try { 329 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService); 330 mService = null; 331 } catch (Throwable t) { 332 Log.w(TAG, "Error cleaning up HAP Client proxy", t); 333 } 334 } 335 } 336 } 337