blob: b4eaab20c5bc6e35ae8983e25d4d6db5a155235f [file] [log] [blame]
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -08001/*
2 * Copyright 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
17package android.bluetooth;
18
19import android.Manifest;
Stanley Tng9d376672019-02-28 12:22:45 -080020import android.annotation.NonNull;
Hansong Zhang7ca303c2018-03-16 09:15:48 -070021import android.annotation.Nullable;
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080022import android.annotation.RequiresPermission;
23import android.annotation.SdkConstant;
24import android.annotation.SdkConstant.SdkConstantType;
Mathew Inwood7acad5e2018-08-01 15:00:35 +010025import android.annotation.UnsupportedAppUsage;
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080026import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.ServiceConnection;
30import android.os.Binder;
31import android.os.IBinder;
32import android.os.RemoteException;
jovanak2a9131f2018-10-15 17:50:19 -070033import android.os.UserHandle;
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080034import android.util.Log;
35
36import com.android.internal.annotations.GuardedBy;
37
38import java.util.ArrayList;
39import java.util.List;
40import java.util.concurrent.locks.ReentrantReadWriteLock;
41
42/**
Stanley Tng1f5ea662018-11-15 17:11:36 -080043 * This class provides the public APIs to control the Hearing Aid profile.
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080044 *
45 * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
46 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
47 * the BluetoothHearingAid proxy object.
48 *
Stanley Tng1f5ea662018-11-15 17:11:36 -080049 * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
50 * method is protected with its appropriate permission.
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080051 */
52public final class BluetoothHearingAid implements BluetoothProfile {
53 private static final String TAG = "BluetoothHearingAid";
Stanley Tng420a0eb2018-11-15 10:22:07 -080054 private static final boolean DBG = true;
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080055 private static final boolean VDBG = false;
56
57 /**
58 * Intent used to broadcast the change in connection state of the Hearing Aid
Stanley Tng1f5ea662018-11-15 17:11:36 -080059 * profile. Please note that in the binaural case, there will be two different LE devices for
60 * the left and right side and each device will have their own connection state changes.S
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080061 *
62 * <p>This intent will have 3 extras:
63 * <ul>
64 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
65 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
66 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
67 * </ul>
68 *
69 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
70 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
71 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
72 *
73 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
74 * receive.
75 */
76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
77 public static final String ACTION_CONNECTION_STATE_CHANGED =
78 "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
79
80 /**
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080081 * Intent used to broadcast the selection of a connected device as active.
82 *
83 * <p>This intent will have one extra:
84 * <ul>
85 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
86 * be null if no device is active. </li>
87 * </ul>
88 *
89 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
90 * receive.
Stanley Tng1f5ea662018-11-15 17:11:36 -080091 *
92 * @hide
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080093 */
94 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mathew Inwood7acad5e2018-08-01 15:00:35 +010095 @UnsupportedAppUsage
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -080096 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
97 "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
98
99 /**
Stanley Tng1f5ea662018-11-15 17:11:36 -0800100 * This device represents Left Hearing Aid.
101 *
102 * @hide
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800103 */
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800104 public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
105
Stanley Tng1f5ea662018-11-15 17:11:36 -0800106 /**
107 * This device represents Right Hearing Aid.
108 *
109 * @hide
110 */
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800111 public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
112
Stanley Tng1f5ea662018-11-15 17:11:36 -0800113 /**
114 * This device is Monaural.
115 *
116 * @hide
117 */
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800118 public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
119
Stanley Tng1f5ea662018-11-15 17:11:36 -0800120 /**
121 * This device is Binaural (should receive only left or right audio).
122 *
123 * @hide
124 */
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800125 public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
126
Stanley Tng1f5ea662018-11-15 17:11:36 -0800127 /**
128 * Indicates the HiSyncID could not be read and is unavailable.
129 *
130 * @hide
131 */
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800132 public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
133
134 private Context mContext;
135 private ServiceListener mServiceListener;
136 private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
137 @GuardedBy("mServiceLock")
138 private IBluetoothHearingAid mService;
139 private BluetoothAdapter mAdapter;
140
141 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
142 new IBluetoothStateChangeCallback.Stub() {
143 public void onBluetoothStateChange(boolean up) {
144 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
145 if (!up) {
146 if (VDBG) Log.d(TAG, "Unbinding service...");
147 try {
148 mServiceLock.writeLock().lock();
149 mService = null;
150 mContext.unbindService(mConnection);
151 } catch (Exception re) {
152 Log.e(TAG, "", re);
153 } finally {
154 mServiceLock.writeLock().unlock();
155 }
156 } else {
157 try {
158 mServiceLock.readLock().lock();
159 if (mService == null) {
160 if (VDBG) Log.d(TAG, "Binding service...");
161 doBind();
162 }
163 } catch (Exception re) {
164 Log.e(TAG, "", re);
165 } finally {
166 mServiceLock.readLock().unlock();
167 }
168 }
169 }
170 };
171
172 /**
173 * Create a BluetoothHearingAid proxy object for interacting with the local
174 * Bluetooth Hearing Aid service.
175 */
176 /*package*/ BluetoothHearingAid(Context context, ServiceListener l) {
177 mContext = context;
178 mServiceListener = l;
179 mAdapter = BluetoothAdapter.getDefaultAdapter();
180 IBluetoothManager mgr = mAdapter.getBluetoothManager();
181 if (mgr != null) {
182 try {
183 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
184 } catch (RemoteException e) {
185 Log.e(TAG, "", e);
186 }
187 }
188
189 doBind();
190 }
191
192 void doBind() {
193 Intent intent = new Intent(IBluetoothHearingAid.class.getName());
194 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
195 intent.setComponent(comp);
196 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
jovanak2a9131f2018-10-15 17:50:19 -0700197 UserHandle.CURRENT_OR_SELF)) {
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800198 Log.e(TAG, "Could not bind to Bluetooth Hearing Aid Service with " + intent);
199 return;
200 }
201 }
202
203 /*package*/ void close() {
204 mServiceListener = null;
205 IBluetoothManager mgr = mAdapter.getBluetoothManager();
206 if (mgr != null) {
207 try {
208 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
209 } catch (Exception e) {
210 Log.e(TAG, "", e);
211 }
212 }
213
214 try {
215 mServiceLock.writeLock().lock();
216 if (mService != null) {
217 mService = null;
218 mContext.unbindService(mConnection);
219 }
220 } catch (Exception re) {
221 Log.e(TAG, "", re);
222 } finally {
223 mServiceLock.writeLock().unlock();
224 }
225 }
226
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800227 /**
228 * Initiate connection to a profile of the remote bluetooth device.
229 *
230 * <p> This API returns false in scenarios like the profile on the
231 * device is already connected or Bluetooth is not turned on.
232 * When this API returns true, it is guaranteed that
233 * connection state intent for the profile will be broadcasted with
234 * the state. Users can get the connection state of the profile
235 * from this intent.
236 *
237 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
238 * permission.
239 *
240 * @param device Remote Bluetooth Device
241 * @return false on immediate error, true otherwise
242 * @hide
243 */
244 public boolean connect(BluetoothDevice device) {
245 if (DBG) log("connect(" + device + ")");
246 try {
247 mServiceLock.readLock().lock();
248 if (mService != null && isEnabled() && isValidDevice(device)) {
249 return mService.connect(device);
250 }
251 if (mService == null) Log.w(TAG, "Proxy not attached to service");
252 return false;
253 } catch (RemoteException e) {
254 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
255 return false;
256 } finally {
257 mServiceLock.readLock().unlock();
258 }
259 }
260
261 /**
262 * Initiate disconnection from a profile
263 *
264 * <p> This API will return false in scenarios like the profile on the
265 * Bluetooth device is not in connected state etc. When this API returns,
266 * true, it is guaranteed that the connection state change
267 * intent will be broadcasted with the state. Users can get the
268 * disconnection state of the profile from this intent.
269 *
270 * <p> If the disconnection is initiated by a remote device, the state
271 * will transition from {@link #STATE_CONNECTED} to
272 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
273 * host (local) device the state will transition from
274 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
275 * state {@link #STATE_DISCONNECTED}. The transition to
276 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
277 * two scenarios.
278 *
279 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
280 * permission.
281 *
282 * @param device Remote Bluetooth Device
283 * @return false on immediate error, true otherwise
284 * @hide
285 */
286 public boolean disconnect(BluetoothDevice device) {
287 if (DBG) log("disconnect(" + device + ")");
288 try {
289 mServiceLock.readLock().lock();
290 if (mService != null && isEnabled() && isValidDevice(device)) {
291 return mService.disconnect(device);
292 }
293 if (mService == null) Log.w(TAG, "Proxy not attached to service");
294 return false;
295 } catch (RemoteException e) {
296 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
297 return false;
298 } finally {
299 mServiceLock.readLock().unlock();
300 }
301 }
302
303 /**
304 * {@inheritDoc}
305 */
306 @Override
Stanley Tng9d376672019-02-28 12:22:45 -0800307 public @NonNull List<BluetoothDevice> getConnectedDevices() {
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800308 if (VDBG) log("getConnectedDevices()");
309 try {
310 mServiceLock.readLock().lock();
311 if (mService != null && isEnabled()) {
312 return mService.getConnectedDevices();
313 }
314 if (mService == null) Log.w(TAG, "Proxy not attached to service");
315 return new ArrayList<BluetoothDevice>();
316 } catch (RemoteException e) {
317 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
318 return new ArrayList<BluetoothDevice>();
319 } finally {
320 mServiceLock.readLock().unlock();
321 }
322 }
323
324 /**
325 * {@inheritDoc}
326 */
Stanley Tng9d376672019-02-28 12:22:45 -0800327 @Override public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
328 @NonNull int[] states) {
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800329 if (VDBG) log("getDevicesMatchingStates()");
330 try {
331 mServiceLock.readLock().lock();
332 if (mService != null && isEnabled()) {
333 return mService.getDevicesMatchingConnectionStates(states);
334 }
335 if (mService == null) Log.w(TAG, "Proxy not attached to service");
336 return new ArrayList<BluetoothDevice>();
337 } catch (RemoteException e) {
338 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
339 return new ArrayList<BluetoothDevice>();
340 } finally {
341 mServiceLock.readLock().unlock();
342 }
343 }
344
345 /**
346 * {@inheritDoc}
347 */
348 @Override
Stanley Tng9d376672019-02-28 12:22:45 -0800349 public int getConnectionState(@NonNull BluetoothDevice device) {
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800350 if (VDBG) log("getState(" + device + ")");
351 try {
352 mServiceLock.readLock().lock();
353 if (mService != null && isEnabled()
354 && isValidDevice(device)) {
355 return mService.getConnectionState(device);
356 }
357 if (mService == null) Log.w(TAG, "Proxy not attached to service");
358 return BluetoothProfile.STATE_DISCONNECTED;
359 } catch (RemoteException e) {
360 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
361 return BluetoothProfile.STATE_DISCONNECTED;
362 } finally {
363 mServiceLock.readLock().unlock();
364 }
365 }
366
367 /**
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700368 * Select a connected device as active.
369 *
370 * The active device selection is per profile. An active device's
371 * purpose is profile-specific. For example, Hearing Aid audio
372 * streaming is to the active Hearing Aid device. If a remote device
373 * is not connected, it cannot be selected as active.
374 *
375 * <p> This API returns false in scenarios like the profile on the
376 * device is not connected or Bluetooth is not turned on.
377 * When this API returns true, it is guaranteed that the
378 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
379 * with the active device.
380 *
381 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
382 * permission.
383 *
384 * @param device the remote Bluetooth device. Could be null to clear
385 * the active device and stop streaming audio to a Bluetooth device.
386 * @return false on immediate error, true otherwise
387 * @hide
388 */
Mathew Inwood7acad5e2018-08-01 15:00:35 +0100389 @UnsupportedAppUsage
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700390 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
391 if (DBG) log("setActiveDevice(" + device + ")");
392 try {
393 mServiceLock.readLock().lock();
394 if (mService != null && isEnabled()
395 && ((device == null) || isValidDevice(device))) {
396 mService.setActiveDevice(device);
397 return true;
398 }
399 if (mService == null) Log.w(TAG, "Proxy not attached to service");
400 return false;
401 } catch (RemoteException e) {
402 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
403 return false;
404 } finally {
405 mServiceLock.readLock().unlock();
406 }
407 }
408
409 /**
Hansong Zhang8d799f82018-03-28 16:53:10 -0700410 * Get the connected physical Hearing Aid devices that are active
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700411 *
412 * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
413 * permission.
414 *
Hansong Zhang8d799f82018-03-28 16:53:10 -0700415 * @return the list of active devices. The first element is the left active
416 * device; the second element is the right active device. If either or both side
417 * is not active, it will be null on that position. Returns empty list on error.
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700418 * @hide
419 */
420 @RequiresPermission(Manifest.permission.BLUETOOTH)
Mathew Inwood7acad5e2018-08-01 15:00:35 +0100421 @UnsupportedAppUsage
Hansong Zhang8d799f82018-03-28 16:53:10 -0700422 public List<BluetoothDevice> getActiveDevices() {
423 if (VDBG) log("getActiveDevices()");
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700424 try {
425 mServiceLock.readLock().lock();
Hansong Zhang8d799f82018-03-28 16:53:10 -0700426 if (mService != null && isEnabled()) {
427 return mService.getActiveDevices();
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700428 }
429 if (mService == null) Log.w(TAG, "Proxy not attached to service");
Hansong Zhang8d799f82018-03-28 16:53:10 -0700430 return new ArrayList<>();
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700431 } catch (RemoteException e) {
432 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
Hansong Zhang8d799f82018-03-28 16:53:10 -0700433 return new ArrayList<>();
Hansong Zhang7ca303c2018-03-16 09:15:48 -0700434 } finally {
435 mServiceLock.readLock().unlock();
436 }
437 }
438
439 /**
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800440 * Set priority of the profile
441 *
442 * <p> The device should already be paired.
443 * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
444 * {@link #PRIORITY_OFF},
445 *
446 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
447 * permission.
448 *
449 * @param device Paired bluetooth device
450 * @param priority
451 * @return true if priority is set, false on error
452 * @hide
453 */
454 public boolean setPriority(BluetoothDevice device, int priority) {
455 if (DBG) log("setPriority(" + device + ", " + priority + ")");
456 try {
457 mServiceLock.readLock().lock();
458 if (mService != null && isEnabled()
459 && isValidDevice(device)) {
460 if (priority != BluetoothProfile.PRIORITY_OFF
461 && priority != BluetoothProfile.PRIORITY_ON) {
462 return false;
463 }
464 return mService.setPriority(device, priority);
465 }
466 if (mService == null) Log.w(TAG, "Proxy not attached to service");
467 return false;
468 } catch (RemoteException e) {
469 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
470 return false;
471 } finally {
472 mServiceLock.readLock().unlock();
473 }
474 }
475
476 /**
477 * Get the priority of the profile.
478 *
479 * <p> The priority can be any of:
480 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
481 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
482 *
483 * @param device Bluetooth device
484 * @return priority of the device
485 * @hide
486 */
487 @RequiresPermission(Manifest.permission.BLUETOOTH)
488 public int getPriority(BluetoothDevice device) {
489 if (VDBG) log("getPriority(" + device + ")");
490 try {
491 mServiceLock.readLock().lock();
492 if (mService != null && isEnabled()
493 && isValidDevice(device)) {
494 return mService.getPriority(device);
495 }
496 if (mService == null) Log.w(TAG, "Proxy not attached to service");
497 return BluetoothProfile.PRIORITY_OFF;
498 } catch (RemoteException e) {
499 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
500 return BluetoothProfile.PRIORITY_OFF;
501 } finally {
502 mServiceLock.readLock().unlock();
503 }
504 }
505
506 /**
507 * Helper for converting a state to a string.
508 *
509 * For debug use only - strings are not internationalized.
510 *
511 * @hide
512 */
513 public static String stateToString(int state) {
514 switch (state) {
515 case STATE_DISCONNECTED:
516 return "disconnected";
517 case STATE_CONNECTING:
518 return "connecting";
519 case STATE_CONNECTED:
520 return "connected";
521 case STATE_DISCONNECTING:
522 return "disconnecting";
Jakub Pawlowskiea580fa2017-11-22 11:02:34 -0800523 default:
524 return "<unknown state " + state + ">";
525 }
526 }
527
528 /**
529 * Get the volume of the device.
530 *
531 * <p> The volume is between -128 dB (mute) to 0 dB.
532 *
533 * @return volume of the hearing aid device.
534 * @hide
535 */
536 @RequiresPermission(Manifest.permission.BLUETOOTH)
537 public int getVolume() {
538 if (VDBG) {
539 log("getVolume()");
540 }
541 try {
542 mServiceLock.readLock().lock();
543 if (mService != null && isEnabled()) {
544 return mService.getVolume();
545 }
546 if (mService == null) Log.w(TAG, "Proxy not attached to service");
547 return 0;
548 } catch (RemoteException e) {
549 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
550 return 0;
551 } finally {
552 mServiceLock.readLock().unlock();
553 }
554 }
555
556 /**
557 * Tells remote device to adjust volume. Uses the following values:
558 * <ul>
559 * <li>{@link AudioManager#ADJUST_LOWER}</li>
560 * <li>{@link AudioManager#ADJUST_RAISE}</li>
561 * <li>{@link AudioManager#ADJUST_MUTE}</li>
562 * <li>{@link AudioManager#ADJUST_UNMUTE}</li>
563 * </ul>
564 *
565 * @param direction One of the supported adjust values.
566 * @hide
567 */
568 @RequiresPermission(Manifest.permission.BLUETOOTH)
569 public void adjustVolume(int direction) {
570 if (DBG) log("adjustVolume(" + direction + ")");
571
572 try {
573 mServiceLock.readLock().lock();
574
575 if (mService == null) {
576 Log.w(TAG, "Proxy not attached to service");
577 return;
578 }
579
580 if (!isEnabled()) return;
581
582 mService.adjustVolume(direction);
583 } catch (RemoteException e) {
584 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
585 } finally {
586 mServiceLock.readLock().unlock();
587 }
588 }
589
590 /**
591 * Tells remote device to set an absolute volume.
592 *
593 * @param volume Absolute volume to be set on remote
594 * @hide
595 */
596 public void setVolume(int volume) {
597 if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
598
599 try {
600 mServiceLock.readLock().lock();
601 if (mService == null) {
602 Log.w(TAG, "Proxy not attached to service");
603 return;
604 }
605
606 if (!isEnabled()) return;
607
608 mService.setVolume(volume);
609 } catch (RemoteException e) {
610 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
611 } finally {
612 mServiceLock.readLock().unlock();
613 }
614 }
615
616 /**
617 * Get the CustomerId of the device.
618 *
619 * @param device Bluetooth device
620 * @return the CustomerId of the device
621 * @hide
622 */
623 @RequiresPermission(Manifest.permission.BLUETOOTH)
624 public long getHiSyncId(BluetoothDevice device) {
625 if (VDBG) {
626 log("getCustomerId(" + device + ")");
627 }
628 try {
629 mServiceLock.readLock().lock();
630 if (mService == null) {
631 Log.w(TAG, "Proxy not attached to service");
632 return HI_SYNC_ID_INVALID;
633 }
634
635 if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
636
637 return mService.getHiSyncId(device);
638 } catch (RemoteException e) {
639 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
640 return HI_SYNC_ID_INVALID;
641 } finally {
642 mServiceLock.readLock().unlock();
643 }
644 }
645
646 /**
647 * Get the side of the device.
648 *
649 * @param device Bluetooth device.
650 * @return SIDE_LEFT or SIDE_RIGHT
651 * @hide
652 */
653 @RequiresPermission(Manifest.permission.BLUETOOTH)
654 public int getDeviceSide(BluetoothDevice device) {
655 if (VDBG) {
656 log("getDeviceSide(" + device + ")");
657 }
658 try {
659 mServiceLock.readLock().lock();
660 if (mService != null && isEnabled()
661 && isValidDevice(device)) {
662 return mService.getDeviceSide(device);
663 }
664 if (mService == null) Log.w(TAG, "Proxy not attached to service");
665 return SIDE_LEFT;
666 } catch (RemoteException e) {
667 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
668 return SIDE_LEFT;
669 } finally {
670 mServiceLock.readLock().unlock();
671 }
672 }
673
674 /**
675 * Get the mode of the device.
676 *
677 * @param device Bluetooth device
678 * @return MODE_MONAURAL or MODE_BINAURAL
679 * @hide
680 */
681 @RequiresPermission(Manifest.permission.BLUETOOTH)
682 public int getDeviceMode(BluetoothDevice device) {
683 if (VDBG) {
684 log("getDeviceMode(" + device + ")");
685 }
686 try {
687 mServiceLock.readLock().lock();
688 if (mService != null && isEnabled()
689 && isValidDevice(device)) {
690 return mService.getDeviceMode(device);
691 }
692 if (mService == null) Log.w(TAG, "Proxy not attached to service");
693 return MODE_MONAURAL;
694 } catch (RemoteException e) {
695 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
696 return MODE_MONAURAL;
697 } finally {
698 mServiceLock.readLock().unlock();
699 }
700 }
701
702 private final ServiceConnection mConnection = new ServiceConnection() {
703 public void onServiceConnected(ComponentName className, IBinder service) {
704 if (DBG) Log.d(TAG, "Proxy object connected");
705 try {
706 mServiceLock.writeLock().lock();
707 mService = IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
708 } finally {
709 mServiceLock.writeLock().unlock();
710 }
711
712 if (mServiceListener != null) {
713 mServiceListener.onServiceConnected(BluetoothProfile.HEARING_AID,
714 BluetoothHearingAid.this);
715 }
716 }
717
718 public void onServiceDisconnected(ComponentName className) {
719 if (DBG) Log.d(TAG, "Proxy object disconnected");
720 try {
721 mServiceLock.writeLock().lock();
722 mService = null;
723 } finally {
724 mServiceLock.writeLock().unlock();
725 }
726 if (mServiceListener != null) {
727 mServiceListener.onServiceDisconnected(BluetoothProfile.HEARING_AID);
728 }
729 }
730 };
731
732 private boolean isEnabled() {
733 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
734 return false;
735 }
736
737 private boolean isValidDevice(BluetoothDevice device) {
738 if (device == null) return false;
739
740 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
741 return false;
742 }
743
744 private static void log(String msg) {
745 Log.d(TAG, msg);
746 }
747}