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