1 /*
2  * Copyright (C) 2018 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.android.cts.verifier.bluetooth;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothHidDevice;
22 import android.bluetooth.BluetoothHidDeviceAppQosSettings;
23 import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.widget.Button;
32 
33 import com.android.cts.verifier.PassFailButtons;
34 import com.android.cts.verifier.R;
35 
36 import java.util.List;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.Executors;
39 
40 public class HidDeviceActivity extends PassFailButtons.Activity {
41     private static final String TAG = HidDeviceActivity.class.getSimpleName();
42     private static final int MSG_APP_STATUS_CHANGED = 0;
43     private static final String SDP_NAME = "CtsVerifier";
44     private static final String SDP_DESCRIPTION = "CtsVerifier HID Device test";
45     private static final String SDP_PROVIDER = "Android";
46     private static final int QOS_TOKEN_RATE = 800; // 9 bytes * 1000000 us / 11250 us
47     private static final int QOS_TOKEN_BUCKET_SIZE = 9;
48     private static final int QOS_PEAK_BANDWIDTH = 0;
49     private static final int QOS_LATENCY = 11250;
50     static final String SAMPLE_INPUT = "bluetooth";
51 
52     private BluetoothAdapter mBluetoothAdapter;
53     private BluetoothHidDevice mBluetoothHidDevice;
54     private BluetoothDevice mHidHost;
55     private ExecutorService mExecutor;
56 
57     private Button mRegisterAppButton;
58     private Button mMakeDiscoverableButton;
59     private Button mUnregisterAppButton;
60     private Button mSendReportButton;
61     private Button mReplyReportButton;
62     private Button mReportErrorButton;
63 
64     private BluetoothProfile.ServiceListener mProfileListener =
65             new BluetoothProfile.ServiceListener() {
66         public void onServiceConnected(int profile, BluetoothProfile proxy) {
67             if (profile == BluetoothProfile.HID_DEVICE) {
68                 mBluetoothHidDevice = (BluetoothHidDevice) proxy;
69             }
70         }
71 
72         public void onServiceDisconnected(int profile) {
73             if (profile == BluetoothProfile.HID_DEVICE) {
74                 mBluetoothHidDevice = null;
75             }
76         }
77     };
78 
79     private final BluetoothHidDeviceAppSdpSettings mSdpSettings =
80             new BluetoothHidDeviceAppSdpSettings(
81                     SDP_NAME,
82                     SDP_DESCRIPTION,
83                     SDP_PROVIDER,
84                     BluetoothHidDevice.SUBCLASS1_COMBO,
85                     HidConstants.HIDD_REPORT_DESC);
86 
87     private final BluetoothHidDeviceAppQosSettings mOutQos =
88             new BluetoothHidDeviceAppQosSettings(
89                     BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,
90                     QOS_TOKEN_RATE,
91                     QOS_TOKEN_BUCKET_SIZE,
92                     QOS_PEAK_BANDWIDTH,
93                     QOS_LATENCY,
94                     BluetoothHidDeviceAppQosSettings.MAX);
95 
96     private BluetoothHidDevice.Callback mCallback = new BluetoothHidDevice.Callback() {
97         @Override
98         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
99             super.onAppStatusChanged(pluggedDevice, registered);
100             Log.d(TAG, "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered="
101                     + registered);
102         }
103 
104         @Override
105         public void onConnectionStateChanged(BluetoothDevice device, int state) {
106             super.onConnectionStateChanged(device, state);
107             Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state);
108         }
109     };
110 
111     @Override
onCreate(Bundle savedInstanceState)112     protected void onCreate(Bundle savedInstanceState) {
113         super.onCreate(savedInstanceState);
114         setContentView(R.layout.bt_hid_device);
115         setPassFailButtonClickListeners();
116         setInfoResources(R.string.bt_hid_device_test_name, R.string.bt_hid_device_test_info, -1);
117 
118         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
119         mBluetoothAdapter.getProfileProxy(getApplicationContext(), mProfileListener,
120                 BluetoothProfile.HID_DEVICE);
121         mExecutor = Executors.newSingleThreadExecutor();
122 
123         mRegisterAppButton = (Button) findViewById(R.id.bt_hid_device_register_button);
124         mRegisterAppButton.setOnClickListener(new OnClickListener() {
125             @Override
126             public void onClick(View v) {
127                 register();
128             }
129         });
130 
131         mUnregisterAppButton = (Button) findViewById(R.id.bt_hid_device_unregister_button);
132         mUnregisterAppButton.setOnClickListener(new OnClickListener() {
133             @Override
134             public void onClick(View v) {
135                 unregister();
136             }
137         });
138 
139         mMakeDiscoverableButton = (Button) findViewById(R.id.bt_hid_device_discoverable_button);
140         mMakeDiscoverableButton.setOnClickListener(new OnClickListener() {
141             @Override
142             public void onClick(View v) {
143                 makeDiscoverable();
144             }
145         });
146 
147         mSendReportButton = (Button) findViewById(R.id.bt_hid_device_send_report_button);
148         mSendReportButton.setOnClickListener(new OnClickListener() {
149             @Override
150             public void onClick(View v) {
151                 testSendReport();
152             }
153         });
154 
155         mReplyReportButton = (Button) findViewById(R.id.bt_hid_device_reply_report_button);
156         mReplyReportButton.setEnabled(false);
157         mReplyReportButton.setOnClickListener(new OnClickListener() {
158             @Override
159             public void onClick(View v) {
160                 testReplyReport();
161             }
162         });
163 
164         mReportErrorButton = (Button) findViewById(R.id.bt_hid_device_report_error_button);
165         mReportErrorButton.setEnabled(false);
166         mReportErrorButton.setOnClickListener(new OnClickListener() {
167             @Override
168             public void onClick(View v) {
169                 testReportError();
170             }
171         });
172 
173         if (isAndroidTv() || isAndroidWear()) {
174             startForegroundService(new Intent(getApplication(),
175                   FocusLossPreventionService.class));
176         }
177     }
178 
179     @Override
onDestroy()180     protected void onDestroy() {
181         super.onDestroy();
182         unregister();
183 
184         if (isAndroidTv() || isAndroidWear()) {
185             stopService(new Intent(getApplication(),
186                   FocusLossPreventionService.class));
187         }
188     }
189 
register()190     private boolean register() {
191         return mBluetoothHidDevice != null
192                 && mBluetoothHidDevice.registerApp(mSdpSettings, null, mOutQos, mExecutor,
193                 mCallback);
194     }
195 
makeDiscoverable()196     private void makeDiscoverable() {
197         if (mBluetoothAdapter.getScanMode() !=
198                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
199             Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
200             intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 30);
201             startActivity(intent);
202         }
203     }
204 
unregister()205     private boolean unregister() {
206         return mBluetoothHidDevice != null && mBluetoothHidDevice.unregisterApp();
207     }
208 
209 
getConnectedDevice()210     private boolean getConnectedDevice() {
211         if (mBluetoothHidDevice == null) {
212             Log.w(TAG, "mBluetoothHidDevice is null");
213             return false;
214         }
215 
216         List<BluetoothDevice> connectedDevices = mBluetoothHidDevice.getConnectedDevices();
217         if (connectedDevices.size() == 0) {
218             return false;
219         } else {
220             return false;
221         }
222     }
223 
testSendReport()224     private void testSendReport() {
225         if (mBluetoothHidDevice == null) {
226             Log.w(TAG, "mBluetoothHidDevice is null");
227             return;
228         }
229 
230         if (mHidHost == null) {
231             if (mBluetoothHidDevice.getConnectedDevices().size() == 0) {
232                 Log.w(TAG, "HID host not connected");
233                 return;
234             } else {
235                 mHidHost = mBluetoothHidDevice.getConnectedDevices().get(0);
236                 Log.d(TAG, "connected to: " + mHidHost);
237             }
238         }
239         for (char c : SAMPLE_INPUT.toCharArray()) {
240             mBluetoothHidDevice.sendReport(mHidHost, BluetoothHidDevice.REPORT_TYPE_INPUT,
241                     singleKeyHit(charToKeyCode(c)));
242             mBluetoothHidDevice.sendReport(mHidHost, BluetoothHidDevice.REPORT_TYPE_INPUT,
243                     singleKeyHit((byte) 0));
244         }
245         mReplyReportButton.setEnabled(true);
246 
247     }
248 
testReplyReport()249     private void testReplyReport() {
250         if (mBluetoothHidDevice == null) {
251             Log.w(TAG, "mBluetoothHidDevice is null");
252             return;
253         }
254 
255         if (mHidHost == null) {
256             if (mBluetoothHidDevice.getConnectedDevices().size() == 0) {
257                 Log.w(TAG, "HID host not connected");
258                 return;
259             } else {
260                 mHidHost = mBluetoothHidDevice.getConnectedDevices().get(0);
261                 Log.d(TAG, "connected to: " + mHidHost);
262             }
263         }
264         if (mBluetoothHidDevice.replyReport(mHidHost, (byte) 0, (byte) 0,
265                 singleKeyHit((byte) 0))) {
266             mReportErrorButton.setEnabled(true);
267         }
268     }
269 
testReportError()270     private void testReportError() {
271         if (mBluetoothHidDevice == null) {
272             Log.w(TAG, "mBluetoothHidDevice is null");
273             return;
274         }
275 
276         if (mHidHost == null) {
277             if (mBluetoothHidDevice.getConnectedDevices().size() == 0) {
278                 Log.w(TAG, "HID host not connected");
279                 return;
280             } else {
281                 mHidHost = mBluetoothHidDevice.getConnectedDevices().get(0);
282                 Log.d(TAG, "connected to: " + mHidHost);
283             }
284         }
285         if (mBluetoothHidDevice.reportError(mHidHost, (byte) 0)) {
286             getPassButton().setEnabled(true);
287         }
288     }
289 
290 
singleKeyHit(byte code)291     private byte[] singleKeyHit(byte code) {
292         byte[] keyboardData = new byte[8];
293         keyboardData[0] = 0;
294         keyboardData[1] = 0;
295         keyboardData[2] = code;
296         keyboardData[3] = 0;
297         keyboardData[4] = 0;
298         keyboardData[5] = 0;
299         keyboardData[6] = 0;
300         keyboardData[7] = 0;
301         return keyboardData;
302     }
303 
charToKeyCode(char c)304     private byte charToKeyCode(char c) {
305         if (c < 'a' || c > 'z') {
306             return 0;
307         }
308         return (byte) (c - 'a' + 0x04);
309     }
310 
isAndroidTv()311     private boolean isAndroidTv() {
312         final PackageManager pm = getApplicationContext().getPackageManager();
313         return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
314                   || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
315     }
316 
isAndroidWear()317     private boolean isAndroidWear() {
318         final PackageManager pm = getApplicationContext().getPackageManager();
319         return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
320     }
321 
322 }
323