Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2011 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 android.bluetooth;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.RemoteException;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 
     34 /**
     35  * This class provides the public APIs to control the Bluetooth Input
     36  * Device Profile.
     37  *
     38  * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
     39  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     40  * the BluetoothHidHost proxy object.
     41  *
     42  * <p>Each method is protected with its appropriate permission.
     43  *
     44  * @hide
     45  */
     46 public final class BluetoothHidHost implements BluetoothProfile {
     47     private static final String TAG = "BluetoothHidHost";
     48     private static final boolean DBG = true;
     49     private static final boolean VDBG = false;
     50 
     51     /**
     52      * Intent used to broadcast the change in connection state of the Input
     53      * Device profile.
     54      *
     55      * <p>This intent will have 3 extras:
     56      * <ul>
     57      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     58      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
     59      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     60      * </ul>
     61      *
     62      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     63      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     64      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     65      *
     66      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     67      * receive.
     68      */
     69     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     70     public static final String ACTION_CONNECTION_STATE_CHANGED =
     71             "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
     72 
     73     /**
     74      * @hide
     75      */
     76     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     77     public static final String ACTION_PROTOCOL_MODE_CHANGED =
     78             "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
     79 
     80     /**
     81      * @hide
     82      */
     83     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     84     public static final String ACTION_HANDSHAKE =
     85             "android.bluetooth.input.profile.action.HANDSHAKE";
     86 
     87     /**
     88      * @hide
     89      */
     90     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     91     public static final String ACTION_REPORT =
     92             "android.bluetooth.input.profile.action.REPORT";
     93 
     94     /**
     95      * @hide
     96      */
     97     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     98     public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
     99             "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
    100 
    101     /**
    102      * @hide
    103      */
    104     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    105     public static final String ACTION_IDLE_TIME_CHANGED =
    106             "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
    107 
    108     /**
    109      * Return codes for the connect and disconnect Bluez / Dbus calls.
    110      *
    111      * @hide
    112      */
    113     public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
    114 
    115     /**
    116      * @hide
    117      */
    118     public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
    119 
    120     /**
    121      * @hide
    122      */
    123     public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
    124 
    125     /**
    126      * @hide
    127      */
    128     public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
    129 
    130     /**
    131      * @hide
    132      */
    133     public static final int INPUT_OPERATION_SUCCESS = 5004;
    134 
    135     /**
    136      * @hide
    137      */
    138     public static final int PROTOCOL_REPORT_MODE = 0;
    139 
    140     /**
    141      * @hide
    142      */
    143     public static final int PROTOCOL_BOOT_MODE = 1;
    144 
    145     /**
    146      * @hide
    147      */
    148     public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
    149 
    150     /*  int reportType, int reportType, int bufferSize */
    151     /**
    152      * @hide
    153      */
    154     public static final byte REPORT_TYPE_INPUT = 1;
    155 
    156     /**
    157      * @hide
    158      */
    159     public static final byte REPORT_TYPE_OUTPUT = 2;
    160 
    161     /**
    162      * @hide
    163      */
    164     public static final byte REPORT_TYPE_FEATURE = 3;
    165 
    166     /**
    167      * @hide
    168      */
    169     public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
    170 
    171     /**
    172      * @hide
    173      */
    174     public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
    175 
    176     /**
    177      * @hide
    178      */
    179     public static final String EXTRA_PROTOCOL_MODE =
    180             "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
    181 
    182     /**
    183      * @hide
    184      */
    185     public static final String EXTRA_REPORT_TYPE =
    186             "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
    187 
    188     /**
    189      * @hide
    190      */
    191     public static final String EXTRA_REPORT_ID =
    192             "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
    193 
    194     /**
    195      * @hide
    196      */
    197     public static final String EXTRA_REPORT_BUFFER_SIZE =
    198             "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
    199 
    200     /**
    201      * @hide
    202      */
    203     public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
    204 
    205     /**
    206      * @hide
    207      */
    208     public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
    209 
    210     /**
    211      * @hide
    212      */
    213     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
    214             "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
    215 
    216     /**
    217      * @hide
    218      */
    219     public static final String EXTRA_IDLE_TIME =
    220             "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
    221 
    222     private Context mContext;
    223     private ServiceListener mServiceListener;
    224     private BluetoothAdapter mAdapter;
    225     private volatile IBluetoothHidHost mService;
    226 
    227     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    228             new IBluetoothStateChangeCallback.Stub() {
    229                 public void onBluetoothStateChange(boolean up) {
    230                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    231                     if (!up) {
    232                         if (VDBG) Log.d(TAG, "Unbinding service...");
    233                         synchronized (mConnection) {
    234                             try {
    235                                 mService = null;
    236                                 mContext.unbindService(mConnection);
    237                             } catch (Exception re) {
    238                                 Log.e(TAG, "", re);
    239                             }
    240                         }
    241                     } else {
    242                         synchronized (mConnection) {
    243                             try {
    244                                 if (mService == null) {
    245                                     if (VDBG) Log.d(TAG, "Binding service...");
    246                                     doBind();
    247                                 }
    248                             } catch (Exception re) {
    249                                 Log.e(TAG, "", re);
    250                             }
    251                         }
    252                     }
    253                 }
    254             };
    255 
    256     /**
    257      * Create a BluetoothHidHost proxy object for interacting with the local
    258      * Bluetooth Service which handles the InputDevice profile
    259      */
    260     /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
    261         mContext = context;
    262         mServiceListener = l;
    263         mAdapter = BluetoothAdapter.getDefaultAdapter();
    264 
    265         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    266         if (mgr != null) {
    267             try {
    268                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    269             } catch (RemoteException e) {
    270                 Log.e(TAG, "", e);
    271             }
    272         }
    273 
    274         doBind();
    275     }
    276 
    277     boolean doBind() {
    278         Intent intent = new Intent(IBluetoothHidHost.class.getName());
    279         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    280         intent.setComponent(comp);
    281         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    282                 mContext.getUser())) {
    283             Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
    284             return false;
    285         }
    286         return true;
    287     }
    288 
    289     /*package*/ void close() {
    290         if (VDBG) log("close()");
    291         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    292         if (mgr != null) {
    293             try {
    294                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    295             } catch (Exception e) {
    296                 Log.e(TAG, "", e);
    297             }
    298         }
    299 
    300         synchronized (mConnection) {
    301             if (mService != null) {
    302                 try {
    303                     mService = null;
    304                     mContext.unbindService(mConnection);
    305                 } catch (Exception re) {
    306                     Log.e(TAG, "", re);
    307                 }
    308             }
    309         }
    310         mServiceListener = null;
    311     }
    312 
    313     /**
    314      * Initiate connection to a profile of the remote bluetooth device.
    315      *
    316      * <p> The system supports connection to multiple input devices.
    317      *
    318      * <p> This API returns false in scenarios like the profile on the
    319      * device is already connected or Bluetooth is not turned on.
    320      * When this API returns true, it is guaranteed that
    321      * connection state intent for the profile will be broadcasted with
    322      * the state. Users can get the connection state of the profile
    323      * from this intent.
    324      *
    325      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    326      * permission.
    327      *
    328      * @param device Remote Bluetooth Device
    329      * @return false on immediate error, true otherwise
    330      * @hide
    331      */
    332     public boolean connect(BluetoothDevice device) {
    333         if (DBG) log("connect(" + device + ")");
    334         final IBluetoothHidHost service = mService;
    335         if (service != null && isEnabled() && isValidDevice(device)) {
    336             try {
    337                 return service.connect(device);
    338             } catch (RemoteException e) {
    339                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    340                 return false;
    341             }
    342         }
    343         if (service == null) Log.w(TAG, "Proxy not attached to service");
    344         return false;
    345     }
    346 
    347     /**
    348      * Initiate disconnection from a profile
    349      *
    350      * <p> This API will return false in scenarios like the profile on the
    351      * Bluetooth device is not in connected state etc. When this API returns,
    352      * true, it is guaranteed that the connection state change
    353      * intent will be broadcasted with the state. Users can get the
    354      * disconnection state of the profile from this intent.
    355      *
    356      * <p> If the disconnection is initiated by a remote device, the state
    357      * will transition from {@link #STATE_CONNECTED} to
    358      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    359      * host (local) device the state will transition from
    360      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    361      * state {@link #STATE_DISCONNECTED}. The transition to
    362      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    363      * two scenarios.
    364      *
    365      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    366      * permission.
    367      *
    368      * @param device Remote Bluetooth Device
    369      * @return false on immediate error, true otherwise
    370      * @hide
    371      */
    372     public boolean disconnect(BluetoothDevice device) {
    373         if (DBG) log("disconnect(" + device + ")");
    374         final IBluetoothHidHost service = mService;
    375         if (service != null && isEnabled() && isValidDevice(device)) {
    376             try {
    377                 return service.disconnect(device);
    378             } catch (RemoteException e) {
    379                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    380                 return false;
    381             }
    382         }
    383         if (service == null) Log.w(TAG, "Proxy not attached to service");
    384         return false;
    385     }
    386 
    387     /**
    388      * {@inheritDoc}
    389      */
    390     @Override
    391     public List<BluetoothDevice> getConnectedDevices() {
    392         if (VDBG) log("getConnectedDevices()");
    393         final IBluetoothHidHost service = mService;
    394         if (service != null && isEnabled()) {
    395             try {
    396                 return service.getConnectedDevices();
    397             } catch (RemoteException e) {
    398                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    399                 return new ArrayList<BluetoothDevice>();
    400             }
    401         }
    402         if (service == null) Log.w(TAG, "Proxy not attached to service");
    403         return new ArrayList<BluetoothDevice>();
    404     }
    405 
    406     /**
    407      * {@inheritDoc}
    408      */
    409     @Override
    410     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    411         if (VDBG) log("getDevicesMatchingStates()");
    412         final IBluetoothHidHost service = mService;
    413         if (service != null && isEnabled()) {
    414             try {
    415                 return service.getDevicesMatchingConnectionStates(states);
    416             } catch (RemoteException e) {
    417                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    418                 return new ArrayList<BluetoothDevice>();
    419             }
    420         }
    421         if (service == null) Log.w(TAG, "Proxy not attached to service");
    422         return new ArrayList<BluetoothDevice>();
    423     }
    424 
    425     /**
    426      * {@inheritDoc}
    427      */
    428     @Override
    429     public int getConnectionState(BluetoothDevice device) {
    430         if (VDBG) log("getState(" + device + ")");
    431         final IBluetoothHidHost service = mService;
    432         if (service != null && isEnabled() && isValidDevice(device)) {
    433             try {
    434                 return service.getConnectionState(device);
    435             } catch (RemoteException e) {
    436                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    437                 return BluetoothProfile.STATE_DISCONNECTED;
    438             }
    439         }
    440         if (service == null) Log.w(TAG, "Proxy not attached to service");
    441         return BluetoothProfile.STATE_DISCONNECTED;
    442     }
    443 
    444     /**
    445      * Set priority of the profile
    446      *
    447      * <p> The device should already be paired.
    448      * Priority can be one of {@link #PRIORITY_ON} or
    449      * {@link #PRIORITY_OFF},
    450      *
    451      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    452      * permission.
    453      *
    454      * @param device Paired bluetooth device
    455      * @param priority
    456      * @return true if priority is set, false on error
    457      * @hide
    458      */
    459     public boolean setPriority(BluetoothDevice device, int priority) {
    460         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    461         final IBluetoothHidHost service = mService;
    462         if (service != null && isEnabled() && isValidDevice(device)) {
    463             if (priority != BluetoothProfile.PRIORITY_OFF
    464                     && priority != BluetoothProfile.PRIORITY_ON) {
    465                 return false;
    466             }
    467             try {
    468                 return service.setPriority(device, priority);
    469             } catch (RemoteException e) {
    470                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    471                 return false;
    472             }
    473         }
    474         if (service == null) Log.w(TAG, "Proxy not attached to service");
    475         return false;
    476     }
    477 
    478     /**
    479      * Get the priority of the profile.
    480      *
    481      * <p> The priority can be any of:
    482      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    483      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    484      *
    485      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    486      *
    487      * @param device Bluetooth device
    488      * @return priority of the device
    489      * @hide
    490      */
    491     public int getPriority(BluetoothDevice device) {
    492         if (VDBG) log("getPriority(" + device + ")");
    493         final IBluetoothHidHost service = mService;
    494         if (service != null && isEnabled() && isValidDevice(device)) {
    495             try {
    496                 return service.getPriority(device);
    497             } catch (RemoteException e) {
    498                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    499                 return BluetoothProfile.PRIORITY_OFF;
    500             }
    501         }
    502         if (service == null) Log.w(TAG, "Proxy not attached to service");
    503         return BluetoothProfile.PRIORITY_OFF;
    504     }
    505 
    506     private final ServiceConnection mConnection = new ServiceConnection() {
    507         public void onServiceConnected(ComponentName className, IBinder service) {
    508             if (DBG) Log.d(TAG, "Proxy object connected");
    509             mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
    510 
    511             if (mServiceListener != null) {
    512                 mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
    513                         BluetoothHidHost.this);
    514             }
    515         }
    516 
    517         public void onServiceDisconnected(ComponentName className) {
    518             if (DBG) Log.d(TAG, "Proxy object disconnected");
    519             mService = null;
    520             if (mServiceListener != null) {
    521                 mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
    522             }
    523         }
    524     };
    525 
    526     private boolean isEnabled() {
    527         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
    528     }
    529 
    530     private static boolean isValidDevice(BluetoothDevice device) {
    531         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    532     }
    533 
    534     /**
    535      * Initiate virtual unplug for a HID input device.
    536      *
    537      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    538      *
    539      * @param device Remote Bluetooth Device
    540      * @return false on immediate error, true otherwise
    541      * @hide
    542      */
    543     public boolean virtualUnplug(BluetoothDevice device) {
    544         if (DBG) log("virtualUnplug(" + device + ")");
    545         final IBluetoothHidHost service = mService;
    546         if (service != null && isEnabled() && isValidDevice(device)) {
    547             try {
    548                 return service.virtualUnplug(device);
    549             } catch (RemoteException e) {
    550                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    551                 return false;
    552             }
    553         }
    554 
    555         if (service == null) Log.w(TAG, "Proxy not attached to service");
    556         return false;
    557 
    558     }
    559 
    560     /**
    561      * Send Get_Protocol_Mode command to the connected HID input device.
    562      *
    563      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    564      *
    565      * @param device Remote Bluetooth Device
    566      * @return false on immediate error, true otherwise
    567      * @hide
    568      */
    569     public boolean getProtocolMode(BluetoothDevice device) {
    570         if (VDBG) log("getProtocolMode(" + device + ")");
    571         final IBluetoothHidHost service = mService;
    572         if (service != null && isEnabled() && isValidDevice(device)) {
    573             try {
    574                 return service.getProtocolMode(device);
    575             } catch (RemoteException e) {
    576                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    577                 return false;
    578             }
    579         }
    580         if (service == null) Log.w(TAG, "Proxy not attached to service");
    581         return false;
    582     }
    583 
    584     /**
    585      * Send Set_Protocol_Mode command to the connected HID input device.
    586      *
    587      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    588      *
    589      * @param device Remote Bluetooth Device
    590      * @return false on immediate error, true otherwise
    591      * @hide
    592      */
    593     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
    594         if (DBG) log("setProtocolMode(" + device + ")");
    595         final IBluetoothHidHost service = mService;
    596         if (service != null && isEnabled() && isValidDevice(device)) {
    597             try {
    598                 return service.setProtocolMode(device, protocolMode);
    599             } catch (RemoteException e) {
    600                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    601                 return false;
    602             }
    603         }
    604         if (service == null) Log.w(TAG, "Proxy not attached to service");
    605         return false;
    606     }
    607 
    608     /**
    609      * Send Get_Report command to the connected HID input device.
    610      *
    611      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    612      *
    613      * @param device Remote Bluetooth Device
    614      * @param reportType Report type
    615      * @param reportId Report ID
    616      * @param bufferSize Report receiving buffer size
    617      * @return false on immediate error, true otherwise
    618      * @hide
    619      */
    620     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
    621             int bufferSize) {
    622         if (VDBG) {
    623             log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
    624                     + "bufferSize=" + bufferSize);
    625         }
    626         final IBluetoothHidHost service = mService;
    627         if (service != null && isEnabled() && isValidDevice(device)) {
    628             try {
    629                 return service.getReport(device, reportType, reportId, bufferSize);
    630             } catch (RemoteException e) {
    631                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    632                 return false;
    633             }
    634         }
    635         if (service == null) Log.w(TAG, "Proxy not attached to service");
    636         return false;
    637     }
    638 
    639     /**
    640      * Send Set_Report command to the connected HID input device.
    641      *
    642      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    643      *
    644      * @param device Remote Bluetooth Device
    645      * @param reportType Report type
    646      * @param report Report receiving buffer size
    647      * @return false on immediate error, true otherwise
    648      * @hide
    649      */
    650     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
    651         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
    652         final IBluetoothHidHost service = mService;
    653         if (service != null && isEnabled() && isValidDevice(device)) {
    654             try {
    655                 return service.setReport(device, reportType, report);
    656             } catch (RemoteException e) {
    657                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    658                 return false;
    659             }
    660         }
    661         if (service == null) Log.w(TAG, "Proxy not attached to service");
    662         return false;
    663     }
    664 
    665     /**
    666      * Send Send_Data command to the connected HID input device.
    667      *
    668      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    669      *
    670      * @param device Remote Bluetooth Device
    671      * @param report Report to send
    672      * @return false on immediate error, true otherwise
    673      * @hide
    674      */
    675     public boolean sendData(BluetoothDevice device, String report) {
    676         if (DBG) log("sendData(" + device + "), report=" + report);
    677         final IBluetoothHidHost service = mService;
    678         if (service != null && isEnabled() && isValidDevice(device)) {
    679             try {
    680                 return service.sendData(device, report);
    681             } catch (RemoteException e) {
    682                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    683                 return false;
    684             }
    685         }
    686         if (service == null) Log.w(TAG, "Proxy not attached to service");
    687         return false;
    688     }
    689 
    690     /**
    691      * Send Get_Idle_Time command to the connected HID input device.
    692      *
    693      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    694      *
    695      * @param device Remote Bluetooth Device
    696      * @return false on immediate error, true otherwise
    697      * @hide
    698      */
    699     public boolean getIdleTime(BluetoothDevice device) {
    700         if (DBG) log("getIdletime(" + device + ")");
    701         final IBluetoothHidHost service = mService;
    702         if (service != null && isEnabled() && isValidDevice(device)) {
    703             try {
    704                 return service.getIdleTime(device);
    705             } catch (RemoteException e) {
    706                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    707                 return false;
    708             }
    709         }
    710         if (service == null) Log.w(TAG, "Proxy not attached to service");
    711         return false;
    712     }
    713 
    714     /**
    715      * Send Set_Idle_Time command to the connected HID input device.
    716      *
    717      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    718      *
    719      * @param device Remote Bluetooth Device
    720      * @param idleTime Idle time to be set on HID Device
    721      * @return false on immediate error, true otherwise
    722      * @hide
    723      */
    724     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
    725         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
    726         final IBluetoothHidHost service = mService;
    727         if (service != null && isEnabled() && isValidDevice(device)) {
    728             try {
    729                 return service.setIdleTime(device, idleTime);
    730             } catch (RemoteException e) {
    731                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    732                 return false;
    733             }
    734         }
    735         if (service == null) Log.w(TAG, "Proxy not attached to service");
    736         return false;
    737     }
    738 
    739     private static void log(String msg) {
    740         Log.d(TAG, msg);
    741     }
    742 }
    743