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