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 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.app.PendingIntent; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.content.res.Configuration; 32 import android.hardware.usb.UsbAccessory; 33 import android.hardware.usb.UsbManager; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Message; 37 import android.os.ParcelFileDescriptor; 38 import android.util.Log; 39 import android.view.View; 40 import android.widget.ArrayAdapter; 41 import android.widget.ListView; 42 import android.widget.Toast; 43 44 import java.io.FileDescriptor; 45 import java.io.FileInputStream; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.io.OutputStream; 50 51 /** 52 * Test for USB accessories. The test activity interacts with a cts-usb-accessory program that 53 * acts as an accessory by exchanging a series of messages. 54 */ 55 public class UsbAccessoryTestActivity extends PassFailButtons.Activity { 56 57 private static final String TAG = UsbAccessoryTestActivity.class.getSimpleName(); 58 59 private static final int FILE_DESCRIPTOR_PROBLEM_DIALOG_ID = 1; 60 61 private static final String ACTION_USB_PERMISSION = 62 "com.android.cts.verifier.usb.USB_PERMISSION"; 63 64 private ArrayAdapter<String> mReceivedMessagesAdapter; 65 private ArrayAdapter<String> mSentMessagesAdapter; 66 private MessageHandler mHandler; 67 68 private UsbManager mUsbManager; 69 private PendingIntent mPermissionIntent; 70 private boolean mPermissionRequestPending; 71 private UsbReceiver mUsbReceiver; 72 73 private ParcelFileDescriptor mFileDescriptor; 74 75 @Override 76 protected void onCreate(Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 setContentView(R.layout.usb_main); 79 setInfoResources(R.string.usb_accessory_test, R.string.usb_accessory_test_info, -1); 80 setPassFailButtonClickListeners(); 81 82 // Don't allow a test pass until the accessory and the Android device exchange messages... 83 getPassButton().setEnabled(false); 84 85 if (!hasUsbAccessorySupport()) { 86 showNoUsbAccessoryDialog(); 87 } 88 89 mReceivedMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); 90 mSentMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); 91 mHandler = new MessageHandler(); 92 93 mUsbManager = (UsbManager) getSystemService(USB_SERVICE); 94 mPermissionIntent = PendingIntent.getBroadcast(this, 0, 95 new Intent(ACTION_USB_PERMISSION), 0); 96 97 mUsbReceiver = new UsbReceiver(); 98 IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); 99 filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); 100 registerReceiver(mUsbReceiver, filter); 101 102 setupListViews(); 103 } 104 105 private boolean hasUsbAccessorySupport() { 106 return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); 107 } 108 109 private void showNoUsbAccessoryDialog() { 110 new AlertDialog.Builder(this) 111 .setIcon(android.R.drawable.ic_dialog_alert) 112 .setTitle(R.string.usb_not_available_title) 113 .setMessage(R.string.usb_not_available_message) 114 .setCancelable(false) 115 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 116 @Override 117 public void onClick(DialogInterface dialog, int which) { 118 finish(); 119 } 120 }) 121 .show(); 122 } 123 124 private void setupListViews() { 125 ListView sentMessages = (ListView) findViewById(R.id.usb_sent_messages); 126 ListView receivedMessages = (ListView) findViewById(R.id.usb_received_messages); 127 128 View emptySentView = findViewById(R.id.usb_empty_sent_messages); 129 View emptyReceivedView = findViewById(R.id.usb_empty_received_messages); 130 sentMessages.setEmptyView(emptySentView); 131 receivedMessages.setEmptyView(emptyReceivedView); 132 133 receivedMessages.setAdapter(mReceivedMessagesAdapter); 134 sentMessages.setAdapter(mSentMessagesAdapter); 135 } 136 137 class UsbReceiver extends BroadcastReceiver { 138 @Override 139 public void onReceive(Context context, Intent intent) { 140 if (ACTION_USB_PERMISSION.equals(intent.getAction()) 141 || UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) { 142 UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 143 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 144 openAccessory(accessory); 145 } else { 146 Log.i(TAG, "Permission denied..."); 147 } 148 mPermissionRequestPending = false; 149 } 150 } 151 } 152 153 private void openAccessory(UsbAccessory accessory) { 154 mFileDescriptor = mUsbManager.openAccessory(accessory); 155 if (mFileDescriptor != null) { 156 FileDescriptor fileDescriptor = mFileDescriptor.getFileDescriptor(); 157 FileInputStream inputStream = new FileInputStream(fileDescriptor); 158 FileOutputStream outputStream = new FileOutputStream(fileDescriptor); 159 new MessageThread(inputStream, outputStream, mHandler).start(); 160 } else { 161 showDialog(FILE_DESCRIPTOR_PROBLEM_DIALOG_ID); 162 } 163 } 164 165 static class MessageThread extends Thread { 166 167 private final InputStream mInputStream; 168 169 private final OutputStream mOutputStream; 170 171 private final MessageHandler mHandler; 172 173 private int mNextMessageNumber = 0; 174 175 MessageThread(InputStream inputStream, OutputStream outputStream, MessageHandler handler) { 176 this.mInputStream = inputStream; 177 this.mOutputStream = outputStream; 178 this.mHandler = handler; 179 } 180 181 @Override 182 public void run() { 183 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_STARTING); 184 185 try { 186 // Wait a bit or else the messages can appear to quick and be confusing... 187 Thread.sleep(2000); 188 sendMessage(); 189 190 // Wait for response and send message acks... 191 int numRead = 0; 192 byte[] buffer = new byte[16384]; 193 while (numRead >= 0) { 194 numRead = mInputStream.read(buffer); 195 if (numRead > 0) { 196 handleReceivedMessage(buffer, numRead); 197 } 198 } 199 } catch (IOException e) { 200 Log.e(TAG, "Exception while reading from input stream", e); 201 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); 202 } catch (InterruptedException e) { 203 Log.e(TAG, "Exception while reading from input stream", e); 204 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); 205 } 206 mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_ENDING); 207 } 208 209 private void handleReceivedMessage(byte[] buffer, int numRead) throws IOException { 210 // TODO: Check the contents of the message? 211 String text = new String(buffer, 0, numRead).trim(); 212 mHandler.sendReceivedMessage(text); 213 214 // Send back a response.. 215 if (mNextMessageNumber <= 10) { 216 sendMessage(); 217 } else { 218 mHandler.sendEmptyMessage(MessageHandler.TEST_PASSED); 219 } 220 } 221 222 private void sendMessage() throws IOException { 223 String text = "Message from Android device #" + mNextMessageNumber++; 224 mOutputStream.write(text.getBytes()); 225 mHandler.sendSentMessage(text); 226 } 227 } 228 229 class MessageHandler extends Handler { 230 231 static final int RECEIVED_MESSAGE = 1; 232 233 static final int SENT_MESSAGE = 2; 234 235 static final int MESSAGE_THREAD_STARTING = 3; 236 237 static final int MESSAGE_THREAD_EXCEPTION = 4; 238 239 static final int MESSAGE_THREAD_ENDING = 5; 240 241 static final int TEST_PASSED = 6; 242 243 @Override 244 public void handleMessage(Message msg) { 245 super.handleMessage(msg); 246 switch (msg.what) { 247 case RECEIVED_MESSAGE: 248 mReceivedMessagesAdapter.add((String) msg.obj); 249 break; 250 251 case SENT_MESSAGE: 252 mSentMessagesAdapter.add((String) msg.obj); 253 break; 254 255 case MESSAGE_THREAD_STARTING: 256 showToast(R.string.usb_message_thread_started); 257 break; 258 259 case MESSAGE_THREAD_EXCEPTION: 260 showToast(R.string.usb_message_thread_exception); 261 break; 262 263 case MESSAGE_THREAD_ENDING: 264 showToast(R.string.usb_message_thread_ended); 265 break; 266 267 case TEST_PASSED: 268 showToast(R.string.usb_test_passed); 269 getPassButton().setEnabled(true); 270 break; 271 272 default: 273 throw new IllegalArgumentException("Bad message type: " + msg.what); 274 } 275 } 276 277 private void showToast(int messageId) { 278 Toast.makeText(UsbAccessoryTestActivity.this, messageId, Toast.LENGTH_SHORT).show(); 279 } 280 281 void sendReceivedMessage(String text) { 282 Message message = Message.obtain(this, RECEIVED_MESSAGE); 283 message.obj = text; 284 sendMessage(message); 285 } 286 287 void sendSentMessage(String text) { 288 Message message = Message.obtain(this, SENT_MESSAGE); 289 message.obj = text; 290 sendMessage(message); 291 } 292 } 293 294 @Override 295 protected void onResume() { 296 super.onResume(); 297 UsbAccessory[] accessories = mUsbManager.getAccessoryList(); 298 UsbAccessory accessory = accessories != null && accessories.length > 0 299 ? accessories[0] 300 : null; 301 if (accessory != null) { 302 if (mUsbManager.hasPermission(accessory)) { 303 openAccessory(accessory); 304 } else { 305 if (!mPermissionRequestPending) { 306 mUsbManager.requestPermission(accessory, mPermissionIntent); 307 mPermissionRequestPending = true; 308 } 309 } 310 } 311 } 312 313 @Override 314 protected void onPause() { 315 super.onPause(); 316 if (mFileDescriptor != null) { 317 try { 318 mFileDescriptor.close(); 319 } catch (IOException e) { 320 Log.e(TAG, "Exception while closing file descriptor", e); 321 } finally { 322 mFileDescriptor = null; 323 } 324 } 325 } 326 327 @Override 328 public void onConfigurationChanged(Configuration newConfig) { 329 super.onConfigurationChanged(newConfig); 330 setContentView(R.layout.usb_main); 331 setupListViews(); 332 } 333 334 @Override 335 public Dialog onCreateDialog(int id, Bundle args) { 336 switch (id) { 337 case FILE_DESCRIPTOR_PROBLEM_DIALOG_ID: 338 return new AlertDialog.Builder(this) 339 .setIcon(android.R.drawable.ic_dialog_alert) 340 .setTitle(R.string.usb_accessory_test) 341 .setMessage(R.string.usb_file_descriptor_error) 342 .create(); 343 344 default: 345 return super.onCreateDialog(id, args); 346 } 347 } 348 349 @Override 350 protected void onDestroy() { 351 super.onDestroy(); 352 unregisterReceiver(mUsbReceiver); 353 } 354 } 355