1 /* 2 * Copyright (C) 2011 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.usb; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.cts.verifier.TestResult; 22 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.app.PendingIntent; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.content.res.Configuration; 33 import android.hardware.usb.UsbAccessory; 34 import android.hardware.usb.UsbManager; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.ParcelFileDescriptor; 40 import android.util.Log; 41 import android.view.View; 42 import android.widget.ArrayAdapter; 43 import android.widget.ListView; 44 import android.widget.Toast; 45 46 import java.io.FileDescriptor; 47 import java.io.FileInputStream; 48 import java.io.FileOutputStream; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.io.OutputStream; 52 53 /** 54 * Test for USB accessories. The test activity interacts with a cts-usb-accessory program that 55 * acts as an accessory by exchanging a series of messages. 56 */ 57 public class UsbAccessoryTestActivity extends PassFailButtons.Activity { 58 59 private static final String TAG = "UsbAccessoryTest"; 60 61 private static final int FILE_DESCRIPTOR_PROBLEM_DIALOG_ID = 1; 62 private static final int STATE_START = 0; 63 private static final int STATE_CONNECTED = 1; 64 private static final int STATE_WAITING_FOR_RECONNECT = 2; 65 private static final int STATE_RECONNECTED = 3; 66 private static final int STATE_PASSED = 4; 67 68 private static final String ACTION_USB_PERMISSION = 69 "com.android.cts.verifier.usb.USB_PERMISSION"; 70 71 private ArrayAdapter<String> mReceivedMessagesAdapter; 72 private ArrayAdapter<String> mSentMessagesAdapter; 73 private MessageHandler mHandler; 74 private Handler mMainHandler; 75 76 private UsbManager mUsbManager; 77 private PendingIntent mPermissionIntent; 78 private boolean mPermissionRequestPending; 79 private UsbReceiver mUsbReceiver; 80 private int mState = STATE_START; 81 private AlertDialog mDisconnectDialog; 82 private AlertDialog mConnectDialog; 83 84 private UsbAccessory mAccessory; 85 private ParcelFileDescriptor mFileDescriptor; 86 87 private Runnable mTimeoutRunnable = new Runnable() { 88 @Override 89 public void run() { 90 Toast.makeText(UsbAccessoryTestActivity.this, 91 R.string.usb_reconnect_timeout, Toast.LENGTH_SHORT).show(); 92 TestResult.setFailedResult(UsbAccessoryTestActivity.this, getTestId(), getTestDetails()); 93 } 94 }; 95 96 @Override onCreate(Bundle savedInstanceState)97 protected void onCreate(Bundle savedInstanceState) { 98 super.onCreate(savedInstanceState); 99 Log.d(TAG, "onCreate"); 100 // Test success only works properly if launched from TestListActivity 101 String action = getIntent().getAction(); 102 if (ACTION_USB_PERMISSION.equals(action) 103 || UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) { 104 finish(); 105 return; 106 } 107 108 setContentView(R.layout.usb_main); 109 setInfoResources(R.string.usb_accessory_test, R.string.usb_accessory_test_info, -1); 110 setPassFailButtonClickListeners(); 111 112 // Don't allow a test pass until the accessory and the Android device exchange messages... 113 getPassButton().setEnabled(false); 114 115 if (!hasUsbAccessorySupport()) { 116 showNoUsbAccessoryDialog(); 117 } 118 119 mReceivedMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); 120 mSentMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); 121 mHandler = new MessageHandler(); 122 123 mUsbManager = (UsbManager) getSystemService(USB_SERVICE); 124 mPermissionIntent = PendingIntent.getBroadcast(this, 0, 125 new Intent(ACTION_USB_PERMISSION), 0); 126 127 mUsbReceiver = new UsbReceiver(); 128 IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); 129 filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); 130 registerReceiver(mUsbReceiver, filter); 131 132 setupListViews(); 133 134 AlertDialog.Builder builder = new AlertDialog.Builder(this) 135 .setIcon(android.R.drawable.ic_dialog_alert) 136 .setTitle(R.string.usb_reconnect_title) 137 .setCancelable(false) 138 .setNegativeButton(R.string.usb_reconnect_abort, 139 new DialogInterface.OnClickListener() { 140 @Override 141 public void onClick(DialogInterface dialog, int which) { 142 setTestResultAndFinish(false); 143 } 144 }); 145 mConnectDialog = builder 146 .setMessage(R.string.usb_connect_message) 147 .create(); 148 mDisconnectDialog = builder 149 .setMessage(R.string.usb_disconnect_message) 150 .create(); 151 152 mMainHandler = new Handler(Looper.getMainLooper()); 153 } 154 hasUsbAccessorySupport()155 private boolean hasUsbAccessorySupport() { 156 return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); 157 } 158 showNoUsbAccessoryDialog()159 private void showNoUsbAccessoryDialog() { 160 new AlertDialog.Builder(this) 161 .setIcon(android.R.drawable.ic_dialog_alert) 162 .setTitle(R.string.usb_not_available_title) 163 .setMessage(R.string.usb_not_available_message) 164 .setCancelable(false) 165 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 166 @Override 167 public void onClick(DialogInterface dialog, int which) { 168 finish(); 169 } 170 }) 171 .show(); 172 } 173 setupListViews()174 private void setupListViews() { 175 ListView sentMessages = (ListView) findViewById(R.id.usb_sent_messages); 176 ListView receivedMessages = (ListView) findViewById(R.id.usb_received_messages); 177 178 View emptySentView = findViewById(R.id.usb_empty_sent_messages); 179 View emptyReceivedView = findViewById(R.id.usb_empty_received_messages); 180 sentMessages.setEmptyView(emptySentView); 181 receivedMessages.setEmptyView(emptyReceivedView); 182 183 receivedMessages.setAdapter(mReceivedMessagesAdapter); 184 sentMessages.setAdapter(mSentMessagesAdapter); 185 } 186 187 class UsbReceiver extends BroadcastReceiver { 188 @Override onReceive(Context context, Intent intent)189 public void onReceive(Context context, Intent intent) { 190 Log.d(TAG, "Received broadcast: intent=" + intent); 191 if (ACTION_USB_PERMISSION.equals(intent.getAction())) { 192 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 193 UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 194 openAccessory(accessory); 195 } else { 196 Log.i(TAG, "Permission denied..."); 197 } 198 mPermissionRequestPending = false; 199 } else if (mState == STATE_WAITING_FOR_RECONNECT && 200 UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(intent.getAction())) { 201 UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 202 if (accessory.equals(mAccessory)) { 203 closeAccessory(); 204 mDisconnectDialog.dismiss(); 205 mConnectDialog.show(); 206 mMainHandler.postDelayed(mTimeoutRunnable, 10000 /* 10 seconds */); 207 } 208 } 209 } 210 } 211 onNewIntent(Intent intent)212 public void onNewIntent(Intent intent) { 213 super.onNewIntent(intent); 214 Log.d(TAG, "onNewIntent: state=" + mState + ", intent=" + intent); 215 if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) { 216 UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 217 openAccessory(accessory); 218 } 219 } 220 openAccessory(UsbAccessory accessory)221 private void openAccessory(UsbAccessory accessory) { 222 mAccessory = accessory; 223 mFileDescriptor = mUsbManager.openAccessory(accessory); 224 if (mState == STATE_START) { 225 setState(STATE_CONNECTED); 226 } else if (mState == STATE_WAITING_FOR_RECONNECT) { 227 setState(STATE_RECONNECTED); 228 mConnectDialog.dismiss(); 229 } 230 if (mFileDescriptor != null) { 231 FileDescriptor fileDescriptor = mFileDescriptor.getFileDescriptor(); 232 FileInputStream inputStream = new FileInputStream(fileDescriptor); 233 FileOutputStream outputStream = new FileOutputStream(fileDescriptor); 234 new MessageThread(inputStream, outputStream, mHandler).start(); 235 } else { 236 showDialog(FILE_DESCRIPTOR_PROBLEM_DIALOG_ID); 237 } 238 } 239 closeAccessory()240 private void closeAccessory() { 241 mAccessory = null; 242 if (mFileDescriptor != null) { 243 try { 244 mFileDescriptor.close(); 245 } catch (IOException e) { 246 Log.e(TAG, "Exception while closing file descriptor", e); 247 } finally { 248 mFileDescriptor = null; 249 } 250 } 251 } 252 253 static class MessageThread extends Thread { 254 255 private final InputStream mInputStream; 256 257 private final OutputStream mOutputStream; 258 259 private final MessageHandler mHandler; 260 261 private int mNextMessageNumber = 0; 262 MessageThread(InputStream inputStream, OutputStream outputStream, MessageHandler handler)263 MessageThread(InputStream inputStream, OutputStream outputStream, MessageHandler handler) { 264 this.mInputStream = inputStream; 265 this.mOutputStream = outputStream; 266 this.mHandler = handler; 267 } 268 269 @Override run()270 public void run() { 271 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_STARTING); 272 273 try { 274 // Wait a bit or else the messages can appear to quick and be confusing... 275 Thread.sleep(2000); 276 sendMessage(); 277 278 // Wait for response and send message acks... 279 int numRead = 0; 280 byte[] buffer = new byte[16384]; 281 boolean done = false; 282 while (numRead >= 0 && !done) { 283 numRead = mInputStream.read(buffer); 284 if (numRead > 0) { 285 done = handleReceivedMessage(buffer, numRead); 286 } 287 } 288 } catch (IOException e) { 289 Log.e(TAG, "Exception while reading from input stream", e); 290 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); 291 } catch (InterruptedException e) { 292 Log.e(TAG, "Exception while reading from input stream", e); 293 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); 294 } 295 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_ENDING); 296 } 297 handleReceivedMessage(byte[] buffer, int numRead)298 private boolean handleReceivedMessage(byte[] buffer, int numRead) throws IOException { 299 // TODO: Check the contents of the message? 300 String text = new String(buffer, 0, numRead).trim(); 301 mHandler.sendReceivedMessage(text); 302 303 // Send back a response.. 304 if (mNextMessageNumber <= 10) { 305 sendMessage(); 306 return false; 307 } else { 308 mHandler.sendEmptyMessage(MessageHandler.STAGE_PASSED); 309 return true; 310 } 311 } 312 sendMessage()313 private void sendMessage() throws IOException { 314 String text = "Message from Android device #" + mNextMessageNumber++; 315 mOutputStream.write(text.getBytes()); 316 mHandler.sendSentMessage(text); 317 } 318 } 319 320 class MessageHandler extends Handler { 321 322 static final int RECEIVED_MESSAGE = 1; 323 324 static final int SENT_MESSAGE = 2; 325 326 static final int MESSAGE_THREAD_STARTING = 3; 327 328 static final int MESSAGE_THREAD_EXCEPTION = 4; 329 330 static final int MESSAGE_THREAD_ENDING = 5; 331 332 static final int STAGE_PASSED = 6; 333 334 @Override handleMessage(Message msg)335 public void handleMessage(Message msg) { 336 super.handleMessage(msg); 337 switch (msg.what) { 338 case RECEIVED_MESSAGE: 339 mReceivedMessagesAdapter.add((String) msg.obj); 340 break; 341 342 case SENT_MESSAGE: 343 mSentMessagesAdapter.add((String) msg.obj); 344 break; 345 346 case MESSAGE_THREAD_STARTING: 347 showToast(R.string.usb_message_thread_started); 348 break; 349 350 case MESSAGE_THREAD_EXCEPTION: 351 showToast(R.string.usb_message_thread_exception); 352 break; 353 354 case MESSAGE_THREAD_ENDING: 355 showToast(R.string.usb_message_thread_ended); 356 break; 357 358 case STAGE_PASSED: 359 if (mState == STATE_RECONNECTED) { 360 showToast(R.string.usb_test_passed); 361 getPassButton().setEnabled(true); 362 setState(STATE_PASSED); 363 mMainHandler.removeCallbacks(mTimeoutRunnable); 364 } else if (mState == STATE_CONNECTED) { 365 mDisconnectDialog.show(); 366 setState(STATE_WAITING_FOR_RECONNECT); 367 } 368 break; 369 370 default: 371 throw new IllegalArgumentException("Bad message type: " + msg.what); 372 } 373 } 374 showToast(int messageId)375 private void showToast(int messageId) { 376 Toast.makeText(UsbAccessoryTestActivity.this, messageId, Toast.LENGTH_SHORT).show(); 377 } 378 sendReceivedMessage(String text)379 void sendReceivedMessage(String text) { 380 Message message = Message.obtain(this, RECEIVED_MESSAGE); 381 message.obj = text; 382 sendMessage(message); 383 } 384 sendSentMessage(String text)385 void sendSentMessage(String text) { 386 Message message = Message.obtain(this, SENT_MESSAGE); 387 message.obj = text; 388 sendMessage(message); 389 } 390 } 391 392 @Override onResume()393 protected void onResume() { 394 super.onResume(); 395 Log.d(TAG, "onResume: state=" + stateToString(mState)); 396 if (mState == STATE_START) { 397 UsbAccessory[] accessories = mUsbManager.getAccessoryList(); 398 UsbAccessory accessory = accessories != null && accessories.length > 0 399 ? accessories[0] 400 : null; 401 if (accessory != null) { 402 if (mUsbManager.hasPermission(accessory)) { 403 openAccessory(accessory); 404 } else { 405 if (!mPermissionRequestPending) { 406 mUsbManager.requestPermission(accessory, mPermissionIntent); 407 mPermissionRequestPending = true; 408 } 409 } 410 } 411 } 412 } 413 414 @Override onPause()415 protected void onPause() { 416 super.onPause(); 417 Log.d(TAG, "onPause: state=" + stateToString(mState)); 418 } 419 420 @Override onStop()421 protected void onStop() { 422 super.onStop(); 423 Log.d(TAG, "onStop: state=" + stateToString(mState)); 424 closeAccessory(); 425 } 426 427 @Override onConfigurationChanged(Configuration newConfig)428 public void onConfigurationChanged(Configuration newConfig) { 429 super.onConfigurationChanged(newConfig); 430 setContentView(R.layout.usb_main); 431 setupListViews(); 432 } 433 434 @Override onCreateDialog(int id, Bundle args)435 public Dialog onCreateDialog(int id, Bundle args) { 436 switch (id) { 437 case FILE_DESCRIPTOR_PROBLEM_DIALOG_ID: 438 return new AlertDialog.Builder(this) 439 .setIcon(android.R.drawable.ic_dialog_alert) 440 .setTitle(R.string.usb_accessory_test) 441 .setMessage(R.string.usb_file_descriptor_error) 442 .create(); 443 444 default: 445 return super.onCreateDialog(id, args); 446 } 447 } 448 449 @Override onDestroy()450 protected void onDestroy() { 451 super.onDestroy(); 452 Log.d(TAG, "onDestroy"); 453 if (mUsbReceiver != null) { 454 unregisterReceiver(mUsbReceiver); 455 } 456 } 457 setState(int newState)458 private void setState(int newState) { 459 Log.d(TAG, "Transition: " + stateToString(mState) + " -> " + stateToString(newState)); 460 mState = newState; 461 } 462 463 stateToString(int state)464 private static String stateToString(int state) { 465 switch (state) { 466 case STATE_START: return "START"; 467 case STATE_CONNECTED: return "CONNECTED"; 468 case STATE_WAITING_FOR_RECONNECT: return "WAITING_FOR_RECONNECT"; 469 case STATE_RECONNECTED: return "RECONNECTED"; 470 case STATE_PASSED: return "PASSED"; 471 default: return "UNKNOWN"; 472 } 473 } 474 475 } 476