Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth;
     18 
     19 import android.app.Service;
     20 import android.bluetooth.BluetoothAdapter;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.bluetooth.BluetoothHidDevice;
     23 import android.bluetooth.BluetoothHidDeviceAppQosSettings;
     24 import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
     25 import android.bluetooth.BluetoothProfile;
     26 import android.bluetooth.BluetoothUuid;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.HandlerThread;
     30 import android.os.ParcelUuid;
     31 
     32 import com.googlecode.android_scripting.Log;
     33 import com.googlecode.android_scripting.facade.EventFacade;
     34 import com.googlecode.android_scripting.facade.FacadeManager;
     35 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     36 import com.googlecode.android_scripting.rpc.Rpc;
     37 import com.googlecode.android_scripting.rpc.RpcParameter;
     38 
     39 import java.util.List;
     40 
     41 public class BluetoothHidDeviceFacade extends RpcReceiver {
     42 
     43     public static final ParcelUuid[] UUIDS = {BluetoothUuid.Hid};
     44 
     45     public static final byte ID_KEYBOARD = 1;
     46     public static final byte ID_MOUSE = 2;
     47 
     48     public static final byte[] HIDD_REPORT_DESC = {
     49             (byte) 0x05,
     50             (byte) 0x01, // Usage page (Generic Desktop)
     51             (byte) 0x09,
     52             (byte) 0x06, // Usage (Keyboard)
     53             (byte) 0xA1,
     54             (byte) 0x01, // Collection (Application)
     55             (byte) 0x85,
     56             ID_KEYBOARD, //    Report ID
     57             (byte) 0x05,
     58             (byte) 0x07, //       Usage page (Key Codes)
     59             (byte) 0x19,
     60             (byte) 0xE0, //       Usage minimum (224)
     61             (byte) 0x29,
     62             (byte) 0xE7, //       Usage maximum (231)
     63             (byte) 0x15,
     64             (byte) 0x00, //       Logical minimum (0)
     65             (byte) 0x25,
     66             (byte) 0x01, //       Logical maximum (1)
     67             (byte) 0x75,
     68             (byte) 0x01, //       Report size (1)
     69             (byte) 0x95,
     70             (byte) 0x08, //       Report count (8)
     71             (byte) 0x81,
     72             (byte) 0x02, //       Input (Data, Variable, Absolute) ; Modifier byte
     73             (byte) 0x75,
     74             (byte) 0x08, //       Report size (8)
     75             (byte) 0x95,
     76             (byte) 0x01, //       Report count (1)
     77             (byte) 0x81,
     78             (byte) 0x01, //       Input (Constant)                 ; Reserved byte
     79             (byte) 0x75,
     80             (byte) 0x08, //       Report size (8)
     81             (byte) 0x95,
     82             (byte) 0x06, //       Report count (6)
     83             (byte) 0x15,
     84             (byte) 0x00, //       Logical Minimum (0)
     85             (byte) 0x25,
     86             (byte) 0x65, //       Logical Maximum (101)
     87             (byte) 0x05,
     88             (byte) 0x07, //       Usage page (Key Codes)
     89             (byte) 0x19,
     90             (byte) 0x00, //       Usage Minimum (0)
     91             (byte) 0x29,
     92             (byte) 0x65, //       Usage Maximum (101)
     93             (byte) 0x81,
     94             (byte) 0x00, //       Input (Data, Array)              ; Key array (6 keys)
     95             (byte) 0xC0, // End Collection
     96             (byte) 0x05,
     97             (byte) 0x01, // Usage Page (Generic Desktop)
     98             (byte) 0x09,
     99             (byte) 0x02, // Usage (Mouse)
    100             (byte) 0xA1,
    101             (byte) 0x01, // Collection (Application)
    102             (byte) 0x85,
    103             ID_MOUSE, //    Report ID
    104             (byte) 0x09,
    105             (byte) 0x01, //    Usage (Pointer)
    106             (byte) 0xA1,
    107             (byte) 0x00, //    Collection (Physical)
    108             (byte) 0x05,
    109             (byte) 0x09, //       Usage Page (Buttons)
    110             (byte) 0x19,
    111             (byte) 0x01, //       Usage minimum (1)
    112             (byte) 0x29,
    113             (byte) 0x03, //       Usage maximum (3)
    114             (byte) 0x15,
    115             (byte) 0x00, //       Logical minimum (0)
    116             (byte) 0x25,
    117             (byte) 0x01, //       Logical maximum (1)
    118             (byte) 0x75,
    119             (byte) 0x01, //       Report size (1)
    120             (byte) 0x95,
    121             (byte) 0x03, //       Report count (3)
    122             (byte) 0x81,
    123             (byte) 0x02, //       Input (Data, Variable, Absolute)
    124             (byte) 0x75,
    125             (byte) 0x05, //       Report size (5)
    126             (byte) 0x95,
    127             (byte) 0x01, //       Report count (1)
    128             (byte) 0x81,
    129             (byte) 0x01, //       Input (constant)                 ; 5 bit padding
    130             (byte) 0x05,
    131             (byte) 0x01, //       Usage page (Generic Desktop)
    132             (byte) 0x09,
    133             (byte) 0x30, //       Usage (X)
    134             (byte) 0x09,
    135             (byte) 0x31, //       Usage (Y)
    136             (byte) 0x09,
    137             (byte) 0x38, //       Usage (Wheel)
    138             (byte) 0x15,
    139             (byte) 0x81, //       Logical minimum (-127)
    140             (byte) 0x25,
    141             (byte) 0x7F, //       Logical maximum (127)
    142             (byte) 0x75,
    143             (byte) 0x08, //       Report size (8)
    144             (byte) 0x95,
    145             (byte) 0x03, //       Report count (3)
    146             (byte) 0x81,
    147             (byte) 0x06, //       Input (Data, Variable, Relative)
    148             (byte) 0xC0, //    End Collection
    149             (byte) 0xC0 // End Collection
    150     };
    151 
    152     // HID mouse movement
    153     private static final byte[] RIGHT = {0, 1, 0, 0};
    154     private static final byte[] DOWN = {0, 0, -1, 0};
    155     private static final byte[] LEFT = {0, -1, 0, 0};
    156     private static final byte[] UP = {0, 0, 1, 0};
    157 
    158     // Default values.
    159     private static final int QOS_TOKEN_RATE = 800; // 9 bytes * 1000000 us / 11250 us
    160     private static final int QOS_TOKEN_BUCKET_SIZE = 9;
    161     private static final int QOS_PEAK_BANDWIDTH = 0;
    162     private static final int QOS_LATENCY = 11250;
    163 
    164     private final Service mService;
    165     private final BluetoothAdapter mBluetoothAdapter;
    166     private final EventFacade mEventFacade;
    167 
    168     private static boolean sIsHidDeviceReady = false;
    169     private static BluetoothHidDevice sHidDeviceProfile = null;
    170     private boolean mKeepMoving = false;
    171 
    172     private final HandlerThread mHandlerThread;
    173     private final Handler mHandler;
    174 
    175     private BluetoothHidDevice.Callback mCallback = new BluetoothHidDevice.Callback() {
    176         @Override
    177         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
    178             Log.d("onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered="
    179                     + registered);
    180             Bundle result = new Bundle();
    181             result.putBoolean("registered", registered);
    182             mEventFacade.postEvent("onAppStatusChanged", result);
    183         }
    184 
    185         @Override
    186         public void onConnectionStateChanged(BluetoothDevice device, int state) {
    187             Log.d("onConnectionStateChanged: device=" + device + " state=" + state);
    188             Bundle result = new Bundle();
    189             result.putInt("state", state);
    190             mEventFacade.postEvent("onConnectionStateChanged", result);
    191         }
    192 
    193         @Override
    194         public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
    195             Log.d("onGetReport: device=" + device + " type=" + type + " id=" + id + " bufferSize="
    196                     + bufferSize);
    197             Bundle result = new Bundle();
    198             result.putByte("type", type);
    199             result.putByte("id", id);
    200             result.putInt("bufferSize", bufferSize);
    201             mEventFacade.postEvent("onGetReport", result);
    202         }
    203 
    204         @Override
    205         public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
    206             Log.d("onSetReport: device=" + device + " type=" + type + " id=" + id);
    207             Bundle result = new Bundle();
    208             result.putByte("type", type);
    209             result.putByte("id", id);
    210             result.putByteArray("data", data);
    211             mEventFacade.postEvent("onSetReport", result);
    212         }
    213 
    214         @Override
    215         public void onSetProtocol(BluetoothDevice device, byte protocol) {
    216             Log.d("onSetProtocol: device=" + device + " protocol=" + protocol);
    217             Bundle result = new Bundle();
    218             result.putByte("protocol", protocol);
    219             mEventFacade.postEvent("onSetProtocol", result);
    220         }
    221 
    222         @Override
    223         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
    224             Log.d("onInterruptData: device=" + device + " reportId=" + reportId);
    225             Bundle result = new Bundle();
    226             result.putByte("registered", reportId);
    227             result.putByteArray("data", data);
    228             mEventFacade.postEvent("onInterruptData", result);
    229         }
    230 
    231         @Override
    232         public void onVirtualCableUnplug(BluetoothDevice device) {
    233             Log.d("onVirtualCableUnplug: device=" + device);
    234             Bundle result = new Bundle();
    235             mEventFacade.postEvent("onVirtualCableUnplug", result);
    236         }
    237     };
    238 
    239     private static BluetoothHidDeviceAppSdpSettings sSdpSettings =
    240             new BluetoothHidDeviceAppSdpSettings("Mock App", "Mock", "Google",
    241                     BluetoothHidDevice.SUBCLASS1_COMBO, HIDD_REPORT_DESC);
    242 
    243     private static BluetoothHidDeviceAppQosSettings sQos =
    244             new BluetoothHidDeviceAppQosSettings(
    245                     BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,
    246                     QOS_TOKEN_RATE,
    247                     QOS_TOKEN_BUCKET_SIZE,
    248                     QOS_PEAK_BANDWIDTH,
    249                     QOS_LATENCY,
    250                     BluetoothHidDeviceAppQosSettings.MAX);
    251 
    252     public BluetoothHidDeviceFacade(FacadeManager manager) {
    253         super(manager);
    254         mService = manager.getService();
    255         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    256         mBluetoothAdapter.getProfileProxy(mService, new HidDeviceServiceListener(),
    257                 BluetoothProfile.HID_DEVICE);
    258         mEventFacade = manager.getReceiver(EventFacade.class);
    259         mHandlerThread = new HandlerThread("BluetoothHidDeviceFacadeHandler",
    260                 android.os.Process.THREAD_PRIORITY_BACKGROUND);
    261         mHandlerThread.start();
    262         mHandler = new Handler(mHandlerThread.getLooper());
    263         Log.w("Init HID Device Facade");
    264     }
    265 
    266     class HidDeviceServiceListener implements BluetoothProfile.ServiceListener {
    267 
    268         @Override
    269         public void onServiceConnected(int profile, BluetoothProfile proxy) {
    270             Log.d("BluetoothHidDeviceFacade: onServiceConnected");
    271             sHidDeviceProfile = (BluetoothHidDevice) proxy;
    272             sIsHidDeviceReady = true;
    273             if (proxy == null) {
    274                 Log.e("proxy is still null");
    275             }
    276         }
    277 
    278         @Override
    279         public void onServiceDisconnected(int profile) {
    280             sIsHidDeviceReady = false;
    281         }
    282     }
    283 
    284     public Boolean hidDeviceConnect(BluetoothDevice device) {
    285         return sHidDeviceProfile != null && sHidDeviceProfile.connect(device);
    286     }
    287 
    288     public Boolean hidDeviceDisconnect(BluetoothDevice device) {
    289         return sHidDeviceProfile != null && sHidDeviceProfile.disconnect(device);
    290     }
    291 
    292     /**
    293      * Check whether the HID Device profile service is ready to use.
    294      * @return true if HID Device profile is ready to use; otherwise false
    295      */
    296     @Rpc(description = "Is HID Device profile ready.")
    297     public Boolean bluetoothHidDeviceIsReady() {
    298         Log.d("isReady");
    299         return sHidDeviceProfile != null && sIsHidDeviceReady;
    300     }
    301 
    302     /**
    303      * Connect to a Bluetooth HID input host.
    304      * @param device name or MAC address or the HID input host
    305      * @return true if successfully connected to the HID host; otherwise false
    306      * @throws Exception error from Bluetooth HidDevService
    307      */
    308     @Rpc(description = "Connect to an HID host.")
    309     public Boolean bluetoothHidDeviceConnect(
    310             @RpcParameter(name = "device",
    311                     description = "Name or MAC address of a bluetooth device.")
    312                     String device)
    313             throws Exception {
    314         if (sHidDeviceProfile == null) {
    315             return false;
    316         }
    317         BluetoothDevice mDevice =
    318                 BluetoothFacade.getDevice(BluetoothFacade.DiscoveredDevices, device);
    319         Log.d("Connecting to device " + mDevice.getAliasName());
    320         return hidDeviceConnect(mDevice);
    321     }
    322 
    323     /**
    324      * Disconnect a Bluetooth HID input host.
    325      * @param device name or MAC address or the HID input host
    326      * @return true if successfully disconnected the HID host; otherwise false
    327      * @throws Exception error from Bluetooth HidDevService
    328      */
    329     @Rpc(description = "Disconnect an HID host.")
    330     public Boolean bluetoothHidDeviceDisconnect(
    331             @RpcParameter(name = "device",
    332                     description = "Name or MAC address of a device.")
    333                     String device)
    334             throws Exception {
    335         if (sHidDeviceProfile == null) {
    336             return false;
    337         }
    338         Log.d("Connected devices: " + sHidDeviceProfile.getConnectedDevices());
    339         BluetoothDevice mDevice = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
    340                 device);
    341         return hidDeviceDisconnect(mDevice);
    342     }
    343 
    344     /**
    345      * Get all the devices connected through HID Device Service.
    346      * @return a list of all the devices connected through HID Device Service,
    347      * or null if the HID device profile is not ready.
    348      */
    349     @Rpc(description = "Get all the devices connected through HID Device Service.")
    350     public List<BluetoothDevice> bluetoothHidDeviceGetConnectedDevices() {
    351         if (sHidDeviceProfile == null) {
    352             return null;
    353         }
    354         return sHidDeviceProfile.getConnectedDevices();
    355     }
    356 
    357     /**
    358      * Get the connection status of the specified device
    359      * @param deviceID name or MAC address or the HID input host
    360      * @return the status of the device
    361      */
    362     @Rpc(description = "Get the connection status of a device.")
    363     public Integer bluetoothHidDeviceGetConnectionStatus(
    364             @RpcParameter(name = "deviceID",
    365                     description = "Name or MAC address of a bluetooth device.")
    366                     String deviceID) {
    367         if (sHidDeviceProfile == null) {
    368             return BluetoothProfile.STATE_DISCONNECTED;
    369         }
    370         List<BluetoothDevice> deviceList = sHidDeviceProfile.getConnectedDevices();
    371         BluetoothDevice device;
    372         try {
    373             device = BluetoothFacade.getDevice(deviceList, deviceID);
    374         } catch (Exception e) {
    375             return BluetoothProfile.STATE_DISCONNECTED;
    376         }
    377         return sHidDeviceProfile.getConnectionState(device);
    378     }
    379 
    380     /**
    381      * Register app for the HID Device service using default settings. This adds a SDP record.
    382      * @return true if successfully registered the app; otherwise false
    383      * @throws Exception error from Bluetooth HidDevService
    384      */
    385     @Rpc(description = "Register app for the HID Device service using default settings.")
    386     public Boolean bluetoothHidDeviceRegisterApp() throws Exception {
    387         return sHidDeviceProfile != null
    388                 && sHidDeviceProfile.registerApp(
    389                         sSdpSettings, null, sQos, command -> command.run(), mCallback);
    390     }
    391 
    392     /**
    393      * Unregister app for the HID Device service.
    394      *
    395      * @return true if successfully unregistered the app; otherwise false
    396      * @throws Exception error from Bluetooth HidDevService
    397      */
    398     @Rpc(description = "Unregister app.")
    399     public Boolean bluetoothHidDeviceUnregisterApp() throws Exception {
    400         return sHidDeviceProfile != null && sHidDeviceProfile.unregisterApp();
    401     }
    402 
    403     /**
    404      * Send a data report to a connected HID host using interrupt channel.
    405      * @param deviceID name or MAC address or the HID input host
    406      * @param id report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
    407      * descriptor.
    408      * @param report report data
    409      * @return true if successfully sent the report; otherwise false
    410      * @throws Exception error from Bluetooth HidDevService
    411      */
    412     @Rpc(description = "Send report to a connected HID host using interrupt channel.")
    413     public Boolean bluetoothHidDeviceSendReport(
    414             @RpcParameter(name = "deviceID",
    415                     description = "Name or MAC address of a bluetooth device.")
    416                     String deviceID,
    417             @RpcParameter(name = "descriptor",
    418                     description = "Descriptor of the report")
    419                     Integer id,
    420             @RpcParameter(name = "report")
    421                     String report) throws Exception {
    422         if (sHidDeviceProfile == null) {
    423             return false;
    424         }
    425 
    426         BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
    427                 deviceID);
    428         byte[] reportByteArray = report.getBytes();
    429         return sHidDeviceProfile.sendReport(device, id, reportByteArray);
    430     }
    431 
    432     /**
    433      * Send a report to the connected HID host as reply for GET_REPORT request from the HID host.
    434      * @param deviceID name or MAC address or the HID input host
    435      * @param type type of the report, as in request
    436      * @param id id of the report, as in request
    437      * @param report report data
    438      * @return true if successfully sent the reply report; otherwise false
    439      * @throws Exception error from Bluetooth HidDevService
    440      */
    441     @Rpc(description = "Send reply report to a connected HID..")
    442     public Boolean bluetoothHidDeviceReplyReport(
    443             @RpcParameter(name = "deviceID",
    444                     description = "Name or MAC address of a bluetooth device.")
    445                     String deviceID,
    446             @RpcParameter(name = "type",
    447                     description = "Type as in the report.")
    448                     Integer type,
    449             @RpcParameter(name = "id",
    450                     description = "id as in the report.")
    451                     Integer id,
    452             @RpcParameter(name = "report")
    453                     String report) throws Exception {
    454         if (sHidDeviceProfile == null) {
    455             return false;
    456         }
    457 
    458         BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
    459                 deviceID);
    460         byte[] reportByteArray = report.getBytes();
    461         return sHidDeviceProfile.replyReport(
    462                 device, (byte) (int) type, (byte) (int) id, reportByteArray);
    463     }
    464 
    465     /**
    466      * Send error handshake message as reply for invalid SET_REPORT request from the HID host.
    467      * @param deviceID name or MAC address or the HID input host
    468      * @param error error byte
    469      * @return true if successfully sent the error handshake message; otherwise false
    470      * @throws Exception error from Bluetooth HidDevService
    471      */
    472     @Rpc(description = "Send error handshake message to a connected HID host.")
    473     public Boolean bluetoothHidDeviceReportError(
    474             @RpcParameter(name = "deviceID",
    475                     description = "Name or MAC address of a bluetooth device.")
    476                     String deviceID,
    477             @RpcParameter(name = "error",
    478                     description = "Error byte")
    479                     Integer error) throws Exception {
    480         if (sHidDeviceProfile == null) {
    481             return false;
    482         }
    483 
    484         BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
    485                 deviceID);
    486         return sHidDeviceProfile.reportError(device, (byte) (int) error);
    487     }
    488 
    489    /**
    490      * Start to send HID mouse input to HID host continuously for given duration.
    491      * @param deviceID name or MAC address for the HID input host
    492      * @param duration time in millisecond to send HID report continuously
    493      * @return true if successfully sent the error handshake message; otherwise false
    494      */
    495     @Rpc(description = "Start to send HID report continuously")
    496     public Boolean bluetoothHidDeviceMoveRepeatedly(
    497             @RpcParameter(name = "deviceID",
    498                     description = "Name or MAC address of a bluetooth device.")
    499                     String deviceID,
    500             @RpcParameter(name = "duration",
    501                     description = "duration")
    502                     Integer duration,
    503             @RpcParameter(name = "interval",
    504                     description = "interval")
    505                     Integer interval) throws Exception {
    506         if (sHidDeviceProfile == null || mKeepMoving) {
    507             return false;
    508         }
    509         BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
    510                 deviceID);
    511         mHandler.post(new Runnable() {
    512             final long mStopTime = System.currentTimeMillis() + duration;
    513             private void sendAndWait(byte[] report) {
    514                 if (!mKeepMoving) {
    515                     return;
    516                 }
    517                 sHidDeviceProfile.sendReport(device, ID_MOUSE, report);
    518                 long endTime = System.currentTimeMillis() + interval;
    519                 while (mKeepMoving && endTime > System.currentTimeMillis()) {
    520                     //Busy waiting
    521                     if (mStopTime < System.currentTimeMillis()) {
    522                         mKeepMoving = false;
    523                         return;
    524                     }
    525                 }
    526             }
    527             public void run() {
    528                 mKeepMoving = true;
    529                 while (mKeepMoving && mStopTime > System.currentTimeMillis()) {
    530                     sendAndWait(RIGHT);
    531                     sendAndWait(DOWN);
    532                     sendAndWait(LEFT);
    533                     sendAndWait(UP);
    534                 }
    535                 mKeepMoving = false;
    536             }
    537         });
    538         return true;
    539     }
    540 
    541     /**
    542      * Stop sending HID report to HID host
    543      */
    544     @Rpc(description = "Stop sending HID report")
    545     public void bluetoothHidDeviceStopMoving() {
    546         mKeepMoving = false;
    547     }
    548 
    549     @Override
    550     public void shutdown() {
    551         Log.w("Quit handler thread");
    552         mHandlerThread.quit();
    553     }
    554 
    555 }
    556