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.CallbackExecutor; 23 import android.annotation.IntDef; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothClass; 26 import android.bluetooth.BluetoothCsipSetCoordinator; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothHapClient; 29 import android.bluetooth.BluetoothHapPresetInfo; 30 import android.bluetooth.BluetoothManager; 31 import android.bluetooth.BluetoothProfile; 32 import android.bluetooth.BluetoothStatusCodes; 33 import android.content.Context; 34 import android.util.Log; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 39 import com.android.settingslib.R; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.concurrent.Executor; 46 47 /** 48 * HapClientProfile handles the Bluetooth HAP service client role. 49 */ 50 public class HapClientProfile implements LocalBluetoothProfile { 51 @Retention(RetentionPolicy.SOURCE) 52 @IntDef(flag = true, value = { 53 HearingAidType.TYPE_INVALID, 54 HearingAidType.TYPE_BINAURAL, 55 HearingAidType.TYPE_MONAURAL, 56 HearingAidType.TYPE_BANDED, 57 HearingAidType.TYPE_RFU 58 }) 59 60 /** Hearing aid type definition for HAP Client. */ 61 public @interface HearingAidType { 62 int TYPE_INVALID = -1; 63 int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL; 64 int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL; 65 int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED; 66 int TYPE_RFU = BluetoothHapClient.TYPE_RFU; 67 } 68 69 static final String NAME = "HapClient"; 70 private static final String TAG = "HapClientProfile"; 71 72 // Order of this profile in device profiles list 73 private static final int ORDINAL = 1; 74 75 private final BluetoothAdapter mBluetoothAdapter; 76 private final CachedBluetoothDeviceManager mDeviceManager; 77 private final LocalBluetoothProfileManager mProfileManager; 78 private BluetoothHapClient mService; 79 private boolean mIsProfileReady; 80 81 // These callbacks run on the main thread. 82 private final class HapClientServiceListener implements BluetoothProfile.ServiceListener { 83 84 @Override onServiceConnected(int profile, BluetoothProfile proxy)85 public void onServiceConnected(int profile, BluetoothProfile proxy) { 86 mService = (BluetoothHapClient) proxy; 87 // We just bound to the service, so refresh the UI for any connected HapClient devices. 88 List<BluetoothDevice> deviceList = mService.getConnectedDevices(); 89 while (!deviceList.isEmpty()) { 90 BluetoothDevice nextDevice = deviceList.remove(0); 91 CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); 92 // Adds a new device into mDeviceManager if it does not exist 93 if (device == null) { 94 Log.w(TAG, "HapClient profile found new device: " + nextDevice); 95 device = mDeviceManager.addDevice(nextDevice); 96 } 97 device.onProfileStateChanged( 98 HapClientProfile.this, BluetoothProfile.STATE_CONNECTED); 99 device.refresh(); 100 } 101 102 // Check current list of CachedDevices to see if any are hearing aid devices. 103 mDeviceManager.updateHearingAidsDevices(); 104 mIsProfileReady = true; 105 mProfileManager.callServiceConnectedListeners(); 106 } 107 108 @Override onServiceDisconnected(int profile)109 public void onServiceDisconnected(int profile) { 110 mIsProfileReady = false; 111 mProfileManager.callServiceDisconnectedListeners(); 112 } 113 } 114 HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager)115 HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager, 116 LocalBluetoothProfileManager profileManager) { 117 mDeviceManager = deviceManager; 118 mProfileManager = profileManager; 119 BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); 120 if (bluetoothManager != null) { 121 mBluetoothAdapter = bluetoothManager.getAdapter(); 122 mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(), 123 BluetoothProfile.HAP_CLIENT); 124 } else { 125 mBluetoothAdapter = null; 126 } 127 } 128 129 /** 130 * Registers a {@link BluetoothHapClient.Callback} that will be invoked during the 131 * operation of this profile. 132 * 133 * Repeated registration of the same <var>callback</var> object after the first call to this 134 * method will result with IllegalArgumentException being thrown, even when the 135 * <var>executor</var> is different. API caller would have to call 136 * {@link #unregisterCallback(BluetoothHapClient.Callback)} with the same callback object 137 * before registering it again. 138 * 139 * @param executor an {@link Executor} to execute given callback 140 * @param callback user implementation of the {@link BluetoothHapClient.Callback} 141 * @throws NullPointerException if a null executor, or callback is given, or 142 * IllegalArgumentException if the same <var>callback</var> is already registered. 143 * @hide 144 */ registerCallback(@onNull @allbackExecutor Executor executor, @NonNull BluetoothHapClient.Callback callback)145 public void registerCallback(@NonNull @CallbackExecutor Executor executor, 146 @NonNull BluetoothHapClient.Callback callback) { 147 if (mService == null) { 148 Log.w(TAG, "Proxy not attached to service. Cannot register callback."); 149 return; 150 } 151 mService.registerCallback(executor, callback); 152 } 153 154 /** 155 * Unregisters the specified {@link BluetoothHapClient.Callback}. 156 * <p>The same {@link BluetoothHapClient.Callback} object used when calling 157 * {@link #registerCallback(Executor, BluetoothHapClient.Callback)} must be used. 158 * 159 * <p>Callbacks are automatically unregistered when application process goes away 160 * 161 * @param callback user implementation of the {@link BluetoothHapClient.Callback} 162 * @throws NullPointerException when callback is null or IllegalArgumentException when no 163 * callback is registered 164 * @hide 165 */ unregisterCallback(@onNull BluetoothHapClient.Callback callback)166 public void unregisterCallback(@NonNull BluetoothHapClient.Callback callback) { 167 if (mService == null) { 168 Log.w(TAG, "Proxy not attached to service. Cannot unregister callback."); 169 return; 170 } 171 mService.unregisterCallback(callback); 172 } 173 174 /** 175 * Gets hearing aid devices matching connection states{ 176 * {@code BluetoothProfile.STATE_CONNECTED}, 177 * {@code BluetoothProfile.STATE_CONNECTING}, 178 * {@code BluetoothProfile.STATE_DISCONNECTING}} 179 * 180 * @return Matching device list 181 */ getConnectedDevices()182 public List<BluetoothDevice> getConnectedDevices() { 183 return getDevicesByStates(new int[] { 184 BluetoothProfile.STATE_CONNECTED, 185 BluetoothProfile.STATE_CONNECTING, 186 BluetoothProfile.STATE_DISCONNECTING}); 187 } 188 189 /** 190 * Gets hearing aid devices matching connection states{ 191 * {@code BluetoothProfile.STATE_DISCONNECTED}, 192 * {@code BluetoothProfile.STATE_CONNECTED}, 193 * {@code BluetoothProfile.STATE_CONNECTING}, 194 * {@code BluetoothProfile.STATE_DISCONNECTING}} 195 * 196 * @return Matching device list 197 */ getConnectableDevices()198 public List<BluetoothDevice> getConnectableDevices() { 199 return getDevicesByStates(new int[] { 200 BluetoothProfile.STATE_DISCONNECTED, 201 BluetoothProfile.STATE_CONNECTED, 202 BluetoothProfile.STATE_CONNECTING, 203 BluetoothProfile.STATE_DISCONNECTING}); 204 } 205 getDevicesByStates(int[] states)206 private List<BluetoothDevice> getDevicesByStates(int[] states) { 207 if (mService == null) { 208 return new ArrayList<>(0); 209 } 210 return mService.getDevicesMatchingConnectionStates(states); 211 } 212 213 /** 214 * Gets the hearing aid type of the device. 215 * 216 * @param device is the device for which we want to get the hearing aid type 217 * @return hearing aid type 218 */ 219 @HearingAidType getHearingAidType(@onNull BluetoothDevice device)220 public int getHearingAidType(@NonNull BluetoothDevice device) { 221 if (mService == null) { 222 return HearingAidType.TYPE_INVALID; 223 } 224 return mService.getHearingAidType(device); 225 } 226 227 /** 228 * Gets if this device supports synchronized presets or not 229 * 230 * @param device is the device for which we want to know if supports synchronized presets 231 * @return {@code true} if the device supports synchronized presets 232 */ supportsSynchronizedPresets(@onNull BluetoothDevice device)233 public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) { 234 if (mService == null) { 235 return false; 236 } 237 return mService.supportsSynchronizedPresets(device); 238 } 239 240 /** 241 * Gets if this device supports independent presets or not 242 * 243 * @param device is the device for which we want to know if supports independent presets 244 * @return {@code true} if the device supports independent presets 245 */ supportsIndependentPresets(@onNull BluetoothDevice device)246 public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) { 247 if (mService == null) { 248 return false; 249 } 250 return mService.supportsIndependentPresets(device); 251 } 252 253 /** 254 * Gets if this device supports dynamic presets or not 255 * 256 * @param device is the device for which we want to know if supports dynamic presets 257 * @return {@code true} if the device supports dynamic presets 258 */ supportsDynamicPresets(@onNull BluetoothDevice device)259 public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) { 260 if (mService == null) { 261 return false; 262 } 263 return mService.supportsDynamicPresets(device); 264 } 265 266 /** 267 * Gets if this device supports writable presets or not 268 * 269 * @param device is the device for which we want to know if supports writable presets 270 * @return {@code true} if the device supports writable presets 271 */ supportsWritablePresets(@onNull BluetoothDevice device)272 public boolean supportsWritablePresets(@NonNull BluetoothDevice device) { 273 if (mService == null) { 274 return false; 275 } 276 return mService.supportsWritablePresets(device); 277 } 278 279 280 /** 281 * Gets the group identifier, which can be used in the group related part of the API. 282 * 283 * <p>Users are expected to get group identifier for each of the connected device to discover 284 * the device grouping. This allows them to make an informed decision which devices can be 285 * controlled by single group API call and which require individual device calls. 286 * 287 * <p>Note that some binaural HA devices may not support group operations, therefore are not 288 * considered a valid HAP group. In such case -1 is returned even if such device is a valid Le 289 * Audio Coordinated Set member. 290 * 291 * @param device is the device for which we want to get the hap group identifier 292 * @return valid group identifier or -1 293 * @hide 294 */ getHapGroup(@onNull BluetoothDevice device)295 public int getHapGroup(@NonNull BluetoothDevice device) { 296 if (mService == null) { 297 Log.w(TAG, "Proxy not attached to service. Cannot get hap group."); 298 return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 299 } 300 return mService.getHapGroup(device); 301 } 302 303 /** 304 * Gets the currently active preset for a HA device. 305 * 306 * @param device is the device for which we want to set the active preset 307 * @return active preset index or {@link BluetoothHapClient#PRESET_INDEX_UNAVAILABLE} if the 308 * device is not connected. 309 * @hide 310 */ getActivePresetIndex(@onNull BluetoothDevice device)311 public int getActivePresetIndex(@NonNull BluetoothDevice device) { 312 if (mService == null) { 313 Log.w(TAG, "Proxy not attached to service. Cannot get active preset index."); 314 return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; 315 } 316 return mService.getActivePresetIndex(device); 317 } 318 319 /** 320 * Gets the currently active preset info for a remote device. 321 * 322 * @param device is the device for which we want to get the preset name 323 * @return currently active preset info if selected, null if preset info is not available for 324 * the remote device 325 * @hide 326 */ 327 @Nullable getActivePresetInfo(@onNull BluetoothDevice device)328 public BluetoothHapPresetInfo getActivePresetInfo(@NonNull BluetoothDevice device) { 329 if (mService == null) { 330 Log.w(TAG, "Proxy not attached to service. Cannot get active preset info."); 331 return null; 332 } 333 return mService.getActivePresetInfo(device); 334 } 335 336 /** 337 * Selects the currently active preset for a HA device 338 * 339 * <p>On success, 340 * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be 341 * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, 342 * {@link BluetoothHapClient.Callback#onPresetSelectionFailed(BluetoothDevice, int)} will be 343 * called. 344 * 345 * @param device is the device for which we want to set the active preset 346 * @param presetIndex is an index of one of the available presets 347 * @hide 348 */ selectPreset(@onNull BluetoothDevice device, int presetIndex)349 public void selectPreset(@NonNull BluetoothDevice device, int presetIndex) { 350 if (mService == null) { 351 Log.w(TAG, "Proxy not attached to service. Cannot select preset."); 352 return; 353 } 354 mService.selectPreset(device, presetIndex); 355 } 356 357 358 /** 359 * Selects the currently active preset for a Hearing Aid device group. 360 * 361 * <p>This group call may replace multiple device calls if those are part of the valid HAS 362 * group. Note that binaural HA devices may or may not support group. 363 * 364 * <p>On success, 365 * {@link BluetoothHapClient.Callback#onPresetSelected(BluetoothDevice, int, int)} will be 366 * called for each device within the group with reason code 367 * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, 368 * {@link BluetoothHapClient.Callback#onPresetSelectionForGroupFailed(int, int)} will be 369 * called for the group. 370 * 371 * @param groupId is the device group identifier for which want to set the active preset 372 * @param presetIndex is an index of one of the available presets 373 * @hide 374 */ selectPresetForGroup(int groupId, int presetIndex)375 public void selectPresetForGroup(int groupId, int presetIndex) { 376 if (mService == null) { 377 Log.w(TAG, "Proxy not attached to service. Cannot select preset for group."); 378 return; 379 } 380 mService.selectPresetForGroup(groupId, presetIndex); 381 } 382 383 /** 384 * Sets the next preset as a currently active preset for a HA device 385 * 386 * <p>Note that the meaning of 'next' is HA device implementation specific and does not 387 * necessarily mean a higher preset index. 388 * 389 * @param device is the device for which we want to set the active preset 390 * @hide 391 */ switchToNextPreset(@onNull BluetoothDevice device)392 public void switchToNextPreset(@NonNull BluetoothDevice device) { 393 if (mService == null) { 394 Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset."); 395 return; 396 } 397 mService.switchToNextPreset(device); 398 } 399 400 401 /** 402 * Sets the next preset as a currently active preset for a HA device group 403 * 404 * <p>Note that the meaning of 'next' is HA device implementation specific and does not 405 * necessarily mean a higher preset index. 406 * 407 * <p>This group call may replace multiple device calls if those are part of the valid HAS 408 * group. Note that binaural HA devices may or may not support group. 409 * 410 * @param groupId is the device group identifier for which want to set the active preset 411 * @hide 412 */ switchToNextPresetForGroup(int groupId)413 public void switchToNextPresetForGroup(int groupId) { 414 if (mService == null) { 415 Log.w(TAG, "Proxy not attached to service. Cannot switch to next preset for group."); 416 return; 417 } 418 mService.switchToNextPresetForGroup(groupId); 419 } 420 421 /** 422 * Sets the previous preset as a currently active preset for a HA device. 423 * 424 * <p>Note that the meaning of 'previous' is HA device implementation specific and does not 425 * necessarily mean a lower preset index. 426 * 427 * @param device is the device for which we want to set the active preset 428 * @hide 429 */ switchToPreviousPreset(@onNull BluetoothDevice device)430 public void switchToPreviousPreset(@NonNull BluetoothDevice device) { 431 if (mService == null) { 432 Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset."); 433 return; 434 } 435 mService.switchToPreviousPreset(device); 436 } 437 438 439 /** 440 * Sets the next preset as a currently active preset for a HA device group 441 * 442 * <p>Note that the meaning of 'next' is HA device implementation specific and does not 443 * necessarily mean a higher preset index. 444 * 445 * <p>This group call may replace multiple device calls if those are part of the valid HAS 446 * group. Note that binaural HA devices may or may not support group. 447 * 448 * @param groupId is the device group identifier for which want to set the active preset 449 * @hide 450 */ switchToPreviousPresetForGroup(int groupId)451 public void switchToPreviousPresetForGroup(int groupId) { 452 if (mService == null) { 453 Log.w(TAG, "Proxy not attached to service. Cannot switch to previous preset for " 454 + "group."); 455 return; 456 } 457 mService.switchToPreviousPresetForGroup(groupId); 458 } 459 460 /** 461 * Requests the preset info 462 * 463 * @param device is the device for which we want to get the preset name 464 * @param presetIndex is an index of one of the available presets 465 * @return preset info 466 * @hide 467 */ getPresetInfo(@onNull BluetoothDevice device, int presetIndex)468 public BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device, int presetIndex) { 469 if (mService == null) { 470 Log.w(TAG, "Proxy not attached to service. Cannot get preset info."); 471 return null; 472 } 473 return mService.getPresetInfo(device, presetIndex); 474 } 475 476 /** 477 * Gets all preset info for a particular device 478 * 479 * @param device is the device for which we want to get all presets info 480 * @return a list of all known preset info 481 * @hide 482 */ 483 @NonNull getAllPresetInfo(@onNull BluetoothDevice device)484 public List<BluetoothHapPresetInfo> getAllPresetInfo(@NonNull BluetoothDevice device) { 485 if (mService == null) { 486 Log.w(TAG, "Proxy not attached to service. Cannot get all preset info."); 487 return new ArrayList<>(); 488 } 489 return mService.getAllPresetInfo(device); 490 } 491 492 /** 493 * Sets the preset name for a particular device 494 * 495 * <p>Note that the name length is restricted to 40 characters. 496 * 497 * <p>On success, 498 * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a 499 * new name will be called and reason code 500 * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, 501 * {@link BluetoothHapClient.Callback#onSetPresetNameFailed(BluetoothDevice, int)} will be 502 * called. 503 * 504 * @param device is the device for which we want to get the preset name 505 * @param presetIndex is an index of one of the available presets 506 * @param name is a new name for a preset, maximum length is 40 characters 507 * @hide 508 */ setPresetName(@onNull BluetoothDevice device, int presetIndex, @NonNull String name)509 public void setPresetName(@NonNull BluetoothDevice device, int presetIndex, 510 @NonNull String name) { 511 if (mService == null) { 512 Log.w(TAG, "Proxy not attached to service. Cannot set preset name."); 513 return; 514 } 515 mService.setPresetName(device, presetIndex, name); 516 } 517 518 /** 519 * Sets the name for a hearing aid preset. 520 * 521 * <p>Note that the name length is restricted to 40 characters. 522 * 523 * <p>On success, 524 * {@link BluetoothHapClient.Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a 525 * new name will be called for each device within the group with reason code 526 * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, 527 * {@link BluetoothHapClient.Callback#onSetPresetNameForGroupFailed(int, int)} will be invoked 528 * 529 * @param groupId is the device group identifier 530 * @param presetIndex is an index of one of the available presets 531 * @param name is a new name for a preset, maximum length is 40 characters 532 * @hide 533 */ setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name)534 public void setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name) { 535 if (mService == null) { 536 Log.w(TAG, "Proxy not attached to service. Cannot set preset name for group."); 537 return; 538 } 539 mService.setPresetNameForGroup(groupId, presetIndex, name); 540 } 541 542 543 @Override accessProfileEnabled()544 public boolean accessProfileEnabled() { 545 return false; 546 } 547 548 @Override isAutoConnectable()549 public boolean isAutoConnectable() { 550 return true; 551 } 552 553 @Override getConnectionStatus(BluetoothDevice device)554 public int getConnectionStatus(BluetoothDevice device) { 555 if (mService == null) { 556 return BluetoothProfile.STATE_DISCONNECTED; 557 } 558 return mService.getConnectionState(device); 559 } 560 561 @Override isEnabled(BluetoothDevice device)562 public boolean isEnabled(BluetoothDevice device) { 563 if (mService == null || device == null) { 564 return false; 565 } 566 return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; 567 } 568 569 @Override getConnectionPolicy(BluetoothDevice device)570 public int getConnectionPolicy(BluetoothDevice device) { 571 if (mService == null || device == null) { 572 return CONNECTION_POLICY_FORBIDDEN; 573 } 574 return mService.getConnectionPolicy(device); 575 } 576 577 @Override setEnabled(BluetoothDevice device, boolean enabled)578 public boolean setEnabled(BluetoothDevice device, boolean enabled) { 579 boolean isSuccessful = false; 580 if (mService == null || device == null) { 581 return false; 582 } 583 if (enabled) { 584 if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { 585 isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); 586 } 587 } else { 588 isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); 589 } 590 591 return isSuccessful; 592 } 593 594 @Override isProfileReady()595 public boolean isProfileReady() { 596 return mIsProfileReady; 597 } 598 599 @Override getProfileId()600 public int getProfileId() { 601 return BluetoothProfile.HAP_CLIENT; 602 } 603 604 @Override getOrdinal()605 public int getOrdinal() { 606 return ORDINAL; 607 } 608 609 @Override getNameResource(BluetoothDevice device)610 public int getNameResource(BluetoothDevice device) { 611 return R.string.bluetooth_profile_hearing_aid; 612 } 613 614 @Override getSummaryResourceForDevice(BluetoothDevice device)615 public int getSummaryResourceForDevice(BluetoothDevice device) { 616 int state = getConnectionStatus(device); 617 switch (state) { 618 case BluetoothProfile.STATE_DISCONNECTED: 619 return R.string.bluetooth_hearing_aid_profile_summary_use_for; 620 621 case BluetoothProfile.STATE_CONNECTED: 622 return R.string.bluetooth_hearing_aid_profile_summary_connected; 623 624 default: 625 return BluetoothUtils.getConnectionStateSummary(state); 626 } 627 } 628 629 @Override getDrawableResource(BluetoothClass btClass)630 public int getDrawableResource(BluetoothClass btClass) { 631 return com.android.internal.R.drawable.ic_bt_hearing_aid; 632 } 633 634 /** 635 * Gets the name of this class 636 * 637 * @return the name of this class 638 */ toString()639 public String toString() { 640 return NAME; 641 } 642 finalize()643 protected void finalize() { 644 Log.d(TAG, "finalize()"); 645 if (mService != null) { 646 try { 647 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService); 648 mService = null; 649 } catch (Throwable t) { 650 Log.w(TAG, "Error cleaning up HAP Client proxy", t); 651 } 652 } 653 } 654 } 655