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