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