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