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 BluetoothHidFacade(FacadeManager manager)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 onServiceConnected(int profile, BluetoothProfile proxy)80 public void onServiceConnected(int profile, BluetoothProfile proxy) { 81 sHidProfile = (BluetoothHidHost) proxy; 82 sIsHidReady = true; 83 } 84 85 @Override onServiceDisconnected(int profile)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 onReceive(Context context, Intent intent)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 */ hidConnect(BluetoothDevice device)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 */ hidDisconnect(BluetoothDevice device)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.") bluetoothHidIsReady()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.") bluetoothHidConnect( @pcParametername = "device", description = "Name or MAC address of a bluetooth device.") String 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.getAlias()); 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.") bluetoothHidDisconnect( @pcParametername = "device", description = "Name or MAC address of a device.") String 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.") bluetoothHidGetConnectedDevices()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.") bluetoothHidGetConnectionStatus( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)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.") 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)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.") 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)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.") bluetoothHidSendData( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID, @RpcParameter(name = "report") String report)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.") bluetoothHidVirtualUnplug( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)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") bluetoothHidSetPriority( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID, @RpcParameter(name = "priority") Integer priority)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") bluetoothHidGetPriority( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)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.") bluetoothHidSetProtocolMode( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID, @RpcParameter(name = "protocolMode") Integer protocolMode)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.") bluetoothHidGetProtocolMode( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)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.") bluetoothHidSetIdleTime( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID, @RpcParameter(name = "idleTime") Integer idleTime)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.") bluetoothHidGetIdleTime( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)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") bluetoothHidStartInputCounter()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") bluetoothHidStopInputCounter()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") bluetoothHidGetCount()463 public double bluetoothHidGetCount() { 464 return mInputCounterTask.getCount(); 465 } 466 467 /** 468 * Test byte transfer. 469 */ 470 @Rpc(description = "Test byte transfer.") testByte()471 public byte[] testByte() { 472 byte[] bts = {0b01, 0b10, 0b11, 0b100}; 473 return bts; 474 } 475 476 @Override shutdown()477 public void shutdown() { 478 mService.unregisterReceiver(mHidServiceBroadcastReceiver); 479 } 480 } 481