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