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.BluetoothHidHost;
     23 import android.bluetooth.BluetoothProfile;
     24 import android.bluetooth.BluetoothUuid;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.os.ParcelUuid;
     30 
     31 import com.googlecode.android_scripting.BaseApplication;
     32 import com.googlecode.android_scripting.FutureActivityTaskExecutor;
     33 import com.googlecode.android_scripting.Log;
     34 import com.googlecode.android_scripting.facade.EventFacade;
     35 import com.googlecode.android_scripting.facade.FacadeManager;
     36 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     37 import com.googlecode.android_scripting.rpc.Rpc;
     38 import com.googlecode.android_scripting.rpc.RpcDefault;
     39 import com.googlecode.android_scripting.rpc.RpcParameter;
     40 
     41 import java.util.List;
     42 
     43 /*
     44  * Class Bluetooth HidFacade
     45  */
     46 public class BluetoothHidFacade extends RpcReceiver {
     47     public static final ParcelUuid[] UUIDS = { BluetoothUuid.Hid };
     48 
     49     private final Service mService;
     50     private final BluetoothAdapter mBluetoothAdapter;
     51     private final FutureActivityTaskExecutor mTaskQueue;
     52     private BluetoothHidInputCounterTask mInputCounterTask;
     53 
     54     private static boolean sIsHidReady = false;
     55     private static BluetoothHidHost sHidProfile = null;
     56 
     57     private final EventFacade mEventFacade;
     58 
     59     public BluetoothHidFacade(FacadeManager manager) {
     60         super(manager);
     61         mService = manager.getService();
     62         mTaskQueue = ((BaseApplication) mService.getApplication()).getTaskExecutor();
     63         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     64         mBluetoothAdapter.getProfileProxy(mService, new HidServiceListener(),
     65         BluetoothProfile.HID_HOST);
     66         IntentFilter pkgFilter = new IntentFilter();
     67         pkgFilter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
     68         pkgFilter.addAction(BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED);
     69         pkgFilter.addAction(BluetoothHidHost.ACTION_HANDSHAKE);
     70         pkgFilter.addAction(BluetoothHidHost.ACTION_REPORT);
     71         pkgFilter.addAction(BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS);
     72         pkgFilter.addAction(BluetoothHidHost.ACTION_IDLE_TIME_CHANGED);
     73         mService.registerReceiver(mHidServiceBroadcastReceiver, pkgFilter);
     74         Log.d(HidServiceBroadcastReceiver.TAG + " registered");
     75         mEventFacade = manager.getReceiver(EventFacade.class);
     76     }
     77 
     78     class HidServiceListener implements BluetoothProfile.ServiceListener {
     79         @Override
     80         public void onServiceConnected(int profile, BluetoothProfile proxy) {
     81             sHidProfile = (BluetoothHidHost) proxy;
     82             sIsHidReady = true;
     83         }
     84 
     85         @Override
     86         public void onServiceDisconnected(int profile) {
     87             sIsHidReady = false;
     88         }
     89     }
     90 
     91     class HidServiceBroadcastReceiver extends BroadcastReceiver {
     92         private static final String TAG = "HidServiceBroadcastReceiver";
     93 
     94         @Override
     95         public void onReceive(Context context, Intent intent) {
     96             String action = intent.getAction();
     97             Log.d(TAG + " action=" + action);
     98 
     99             switch (action) {
    100                 case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED: {
    101                     int previousState = intent.getIntExtra(
    102                             BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
    103                     int state = intent.getIntExtra(
    104                             BluetoothProfile.EXTRA_STATE, -1);
    105                     Log.d("Connection state changed: "
    106                             + previousState + " -> " + state);
    107                 }
    108                 break;
    109                 case BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED: {
    110                     int status = intent.getIntExtra(
    111                             BluetoothHidHost.EXTRA_STATUS, -1);
    112                     Log.d("Protocol mode changed: " + status);
    113                 }
    114                 break;
    115                 case BluetoothHidHost.ACTION_HANDSHAKE: {
    116                     int status = intent.getIntExtra(
    117                             BluetoothHidHost.EXTRA_STATUS, -1);
    118                     Log.d("Handshake received: " + status);
    119                 }
    120                 break;
    121                 case BluetoothHidHost.ACTION_REPORT: {
    122                     char[] report = intent.getCharArrayExtra(
    123                             BluetoothHidHost.EXTRA_REPORT);
    124                     Log.d("Received report: " + String.valueOf(report));
    125                 }
    126                 break;
    127                 case BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS: {
    128                     int status = intent.getIntExtra(
    129                             BluetoothHidHost.EXTRA_VIRTUAL_UNPLUG_STATUS, -1);
    130                     Log.d("Virtual unplug status: " + status);
    131                 }
    132                 break;
    133                 case BluetoothHidHost.ACTION_IDLE_TIME_CHANGED: {
    134                     int idleTime = intent.getIntExtra(
    135                             BluetoothHidHost.EXTRA_IDLE_TIME, -1);
    136                     Log.d("Idle time changed: " + idleTime);
    137                 }
    138                 break;
    139                 default:
    140                     break;
    141             }
    142         }
    143     }
    144 
    145     private final BroadcastReceiver mHidServiceBroadcastReceiver =
    146             new HidServiceBroadcastReceiver();
    147 
    148     /**
    149      * Connect to Hid Profile.
    150      * @param device - the Bluetooth Device object to connect to.
    151      * @return if the connection was successfull or not.
    152      */
    153     public Boolean hidConnect(BluetoothDevice device) {
    154         if (sHidProfile == null) return false;
    155         return sHidProfile.connect(device);
    156     }
    157 
    158     /**
    159      * Disconnect to Hid Profile.
    160      * @param device - the Bluetooth Device object to disconnect to.
    161      * @return if the disconnection was successfull or not.
    162      */
    163     public Boolean hidDisconnect(BluetoothDevice device) {
    164         if (sHidProfile == null) return false;
    165         return sHidProfile.disconnect(device);
    166     }
    167 
    168     /**
    169      * Is Hid profile ready.
    170      * @return if Hid profile is ready or not.
    171      */
    172     @Rpc(description = "Is Hid profile ready.")
    173     public Boolean bluetoothHidIsReady() {
    174         return sIsHidReady;
    175     }
    176 
    177     /**
    178      * Connect to an HID device.
    179      * @param device - Name or MAC address of a bluetooth device.
    180      * @return if the connection was successfull or not.
    181      */
    182     @Rpc(description = "Connect to an HID device.")
    183     public Boolean bluetoothHidConnect(
    184             @RpcParameter(name = "device",
    185                 description = "Name or MAC address of a bluetooth device.")
    186                     String device)
    187                         throws Exception {
    188         if (sHidProfile == null) return false;
    189         BluetoothDevice mDevice = BluetoothFacade.getDevice(
    190                 BluetoothFacade.DiscoveredDevices, device);
    191         Log.d("Connecting to device " + mDevice.getAliasName());
    192         return hidConnect(mDevice);
    193     }
    194 
    195     /**
    196      * Disconnect an HID device.
    197      * @param device - the Bluetooth Device object to disconnect to.
    198      * @return if the disconnection was successfull or not.
    199      */
    200     @Rpc(description = "Disconnect an HID device.")
    201     public Boolean bluetoothHidDisconnect(
    202             @RpcParameter(name = "device",
    203                 description = "Name or MAC address of a device.")
    204                     String device)
    205                         throws Exception {
    206         if (sHidProfile == null) return false;
    207         Log.d("Connected devices: " + sHidProfile.getConnectedDevices());
    208         BluetoothDevice mDevice = BluetoothFacade.getDevice(
    209                 sHidProfile.getConnectedDevices(), device);
    210         return hidDisconnect(mDevice);
    211     }
    212 
    213     /**
    214      * Get all the devices connected through HID.
    215      * @return List of all the devices connected through HID.
    216      */
    217     @Rpc(description = "Get all the devices connected through HID.")
    218     public List<BluetoothDevice> bluetoothHidGetConnectedDevices() {
    219         if (!sIsHidReady) return null;
    220         return sHidProfile.getConnectedDevices();
    221     }
    222 
    223     /**
    224      * Get the connection status of a device.
    225      * @param deviceID - Name or MAC address of a bluetooth device.
    226      * @return connection status of a device.
    227      */
    228     @Rpc(description = "Get the connection status of a device.")
    229     public Integer bluetoothHidGetConnectionStatus(
    230             @RpcParameter(name = "deviceID",
    231                 description = "Name or MAC address of a bluetooth device.")
    232                     String deviceID) {
    233         if (sHidProfile == null) {
    234             return BluetoothProfile.STATE_DISCONNECTED;
    235         }
    236         List<BluetoothDevice> deviceList = sHidProfile.getConnectedDevices();
    237         BluetoothDevice device;
    238         try {
    239             device = BluetoothFacade.getDevice(deviceList, deviceID);
    240         } catch (Exception e) {
    241             return BluetoothProfile.STATE_DISCONNECTED;
    242         }
    243         return sHidProfile.getConnectionState(device);
    244     }
    245 
    246     /**
    247      * Send Set_Report command to the connected HID input device.
    248      * @param deviceID - Name or MAC address of a bluetooth device.
    249      * @return True if successfully sent the command; otherwise false
    250      */
    251     @Rpc(description =
    252             "Send Set_Report command to the connected HID input device.")
    253     public Boolean bluetoothHidSetReport(
    254             @RpcParameter(name = "deviceID",
    255                 description = "Name or MAC address of a bluetooth device.")
    256                     String deviceID,
    257             @RpcParameter(name = "type")
    258             @RpcDefault(value = "1") Integer type,
    259             @RpcParameter(name = "report")
    260                 String report) throws Exception {
    261         BluetoothDevice device = BluetoothFacade.getDevice(
    262                 sHidProfile.getConnectedDevices(), deviceID);
    263         Log.d("type=" + type);
    264         return sHidProfile.setReport(device, (byte) (int) type, report);
    265     }
    266 
    267     /**
    268      * Sends the Get_Report command to the given connected HID input device.
    269      * @param deviceID name or MAC address or the HID input device
    270      * @param type Bluetooth HID report type
    271      * @param reportId ID for the requesting report
    272      * @param buffSize advised buffer size on the Bluetooth HID host
    273      * @return True if successfully sent the command; otherwise false
    274      * @throws Exception error from Bluetooth HidService
    275      */
    276     @Rpc(description = "Send Get_Report command to the connected HID input device.")
    277     public Boolean bluetoothHidGetReport(
    278             @RpcParameter(name = "deviceID",
    279                 description = "Name or MAC address of a bluetooth device.")
    280                     String deviceID,
    281             @RpcParameter(name = "type")
    282             @RpcDefault(value = "1") Integer type,
    283             @RpcParameter(name = "reportId")
    284             Integer reportId,
    285             @RpcParameter(name = "buffSize")
    286             Integer buffSize) throws Exception {
    287         BluetoothDevice device = BluetoothFacade.getDevice(
    288                 sHidProfile.getConnectedDevices(), deviceID);
    289         Log.d("type=" + type + " reportId=" + reportId);
    290         return sHidProfile.getReport(
    291                 device, (byte) (int) type, (byte) (int) reportId, buffSize);
    292     }
    293 
    294     /**
    295      * Sends a data report to the given connected HID input device.
    296      * @param deviceID name or MAC address or the HID input device
    297      * @param report the report payload
    298      * @return True if successfully sent the command; otherwise false
    299      * @throws Exception error from Bluetooth HidService
    300      */
    301     @Rpc(description = "Send data to a connected HID device.")
    302     public Boolean bluetoothHidSendData(
    303             @RpcParameter(name = "deviceID",
    304                 description = "Name or MAC address of a bluetooth device.")
    305                 String deviceID,
    306             @RpcParameter(name = "report")
    307                 String report) throws Exception {
    308         BluetoothDevice device = BluetoothFacade.getDevice(
    309                 sHidProfile.getConnectedDevices(), deviceID);
    310         return sHidProfile.sendData(device, report);
    311     }
    312 
    313 
    314     /**
    315      * Sends the virtual cable unplug command to the given connected HID input device.
    316      * @param deviceID name or MAC address or the HID input device
    317      * @return True if successfully sent the command; otherwise false
    318      * @throws Exception error from Bluetooth HidService
    319      */
    320     @Rpc(description = "Send virtual unplug to a connected HID device.")
    321         public Boolean bluetoothHidVirtualUnplug(
    322                 @RpcParameter(name = "deviceID",
    323           description = "Name or MAC address of a bluetooth device.")
    324           String deviceID) throws Exception {
    325         BluetoothDevice device = BluetoothFacade.getDevice(sHidProfile.getConnectedDevices(),
    326               deviceID);
    327         return sHidProfile.virtualUnplug(device);
    328     }
    329 
    330     /**
    331      * Sends the Set_Priority command to the given connected HID input device.
    332      * @param deviceID name or MAC address or the HID input device
    333      * @param priority priority level
    334      * @return True if successfully sent the command; otherwise false
    335      * @throws Exception error from Bluetooth HidService
    336      */
    337     @Rpc(description = "Set priority of the profile")
    338     public Boolean bluetoothHidSetPriority(
    339           @RpcParameter(name = "deviceID",
    340                   description = "Name or MAC address of a bluetooth device.")
    341                   String deviceID,
    342           @RpcParameter(name = "priority")
    343                   Integer priority) throws Exception {
    344         BluetoothDevice device = BluetoothFacade.getDevice(sHidProfile.getConnectedDevices(),
    345               deviceID);
    346         return sHidProfile.setPriority(device, priority);
    347     }
    348 
    349     /**
    350      * Sends the Get_Priority command to the given connected HID input device.
    351      * @param deviceID name or MAC address or the HID input device
    352      * @return The value of the HID input device priority
    353      * @throws Exception error from Bluetooth HidService
    354      */
    355     @Rpc(description = "Get priority of the profile")
    356     public Integer bluetoothHidGetPriority(
    357           @RpcParameter(name = "deviceID",
    358                   description = "Name or MAC address of a bluetooth device.")
    359                   String deviceID) throws Exception {
    360         BluetoothDevice device = BluetoothFacade.getDevice(sHidProfile.getConnectedDevices(),
    361               deviceID);
    362         return sHidProfile.getPriority(device);
    363     }
    364 
    365     /**
    366      * Sends the Set_Protocol_Mode command to the given connected HID input device.
    367      * @param deviceID name or MAC address or the HID input device
    368      * @param protocolMode protocol mode
    369      * @return True if successfully sent the command; otherwise false
    370      * @throws Exception error from Bluetooth HidService
    371      */
    372     @Rpc(description = "Send Set_Protocol_Mode command to the connected HID input device.")
    373     public Boolean bluetoothHidSetProtocolMode(
    374           @RpcParameter(name = "deviceID",
    375                   description = "Name or MAC address of a bluetooth device.")
    376                   String deviceID,
    377           @RpcParameter(name = "protocolMode")
    378                   Integer protocolMode) throws Exception {
    379         BluetoothDevice device = BluetoothFacade.getDevice(sHidProfile.getConnectedDevices(),
    380               deviceID);
    381         return sHidProfile.setProtocolMode(device, protocolMode);
    382     }
    383 
    384     /**
    385      * Sends the Get_Protocol_Mode command to the given connected HID input device.
    386      * @param deviceID name or MAC address or the HID input device
    387      * @return True if successfully sent the command; otherwise false
    388      * @throws Exception error from Bluetooth HidService
    389      */
    390     @Rpc(description =
    391             "Send Get_Protocol_Mode command to the connected HID input device.")
    392     public Boolean bluetoothHidGetProtocolMode(
    393           @RpcParameter(name = "deviceID",
    394                   description = "Name or MAC address of a bluetooth device.")
    395                   String deviceID) throws Exception {
    396         BluetoothDevice device = BluetoothFacade.getDevice(
    397                 sHidProfile.getConnectedDevices(), deviceID);
    398         return sHidProfile.getProtocolMode(device);
    399     }
    400 
    401     /**
    402      * Sends the Set_Idle_Time command to the given connected HID input device.
    403      * @param deviceID name or MAC address or the HID input device
    404      * @param idleTime idle time
    405      * @return True if successfully sent the command; otherwise false
    406      * @throws Exception error from Bluetooth HidService
    407      */
    408     @Rpc(description = "Send Set_Idle_Time command to the connected HID input device.")
    409     public Boolean bluetoothHidSetIdleTime(
    410             @RpcParameter(name = "deviceID",
    411                 description = "Name or MAC address of a bluetooth device.")
    412                     String deviceID,
    413             @RpcParameter(name = "idleTime")
    414                 Integer idleTime) throws Exception {
    415         BluetoothDevice device = BluetoothFacade.getDevice(
    416                 sHidProfile.getConnectedDevices(), deviceID);
    417         return sHidProfile.setIdleTime(
    418                 device, (byte) (int) idleTime);
    419     }
    420 
    421     /**
    422      * Sends the Get_Idle_Time command to the given connected HID input device.
    423      * @param deviceID name or MAC address or the HID input device
    424      * @return True if successfully sent the command; otherwise false
    425      * @throws Exception error from Bluetooth HidService
    426      */
    427     @Rpc(description = "Send Get_Idle_Time command to the connected HID input device.")
    428     public Boolean bluetoothHidGetIdleTime(
    429             @RpcParameter(name = "deviceID",
    430                   description = "Name or MAC address of a bluetooth device.")
    431                   String deviceID) throws Exception {
    432         BluetoothDevice device = BluetoothFacade.getDevice(
    433                 sHidProfile.getConnectedDevices(), deviceID);
    434         return sHidProfile.getIdleTime(device);
    435     }
    436 
    437     /**
    438      * Start to monitor HID device input count
    439      */
    440     @Rpc(description = "Start keyboard/mouse input counter")
    441     public void bluetoothHidStartInputCounter() throws InterruptedException {
    442         mInputCounterTask = new BluetoothHidInputCounterTask();
    443         mTaskQueue.execute(mInputCounterTask);
    444         mInputCounterTask.getShowLatch().await();
    445     }
    446 
    447     /**
    448      * Stop to monitor HID device input count
    449      */
    450     @Rpc(description = "Stop keyboard/mouse input rate checker")
    451     public void bluetoothHidStopInputCounter() throws InterruptedException {
    452         if (mInputCounterTask != null) {
    453             mInputCounterTask.finish();
    454             mInputCounterTask = null;
    455         }
    456     }
    457 
    458     /**
    459      * Get HID device input rate
    460      * @return The value of HID device input count during the first and the last input.
    461      */
    462     @Rpc(description = "Get HID keyboard/mouse input count")
    463     public double bluetoothHidGetCount() {
    464         return mInputCounterTask.getCount();
    465     }
    466 
    467     /**
    468      * Test byte transfer.
    469      */
    470     @Rpc(description = "Test byte transfer.")
    471     public byte[] testByte() {
    472         byte[] bts = {0b01, 0b10, 0b11, 0b100};
    473         return bts;
    474     }
    475 
    476     @Override
    477     public void shutdown() {
    478         mService.unregisterReceiver(mHidServiceBroadcastReceiver);
    479     }
    480 }
    481