| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifier.usb; |
| |
| import com.android.cts.verifier.PassFailButtons; |
| import com.android.cts.verifier.R; |
| import com.android.cts.verifier.TestResult; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.hardware.usb.UsbAccessory; |
| import android.hardware.usb.UsbManager; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.ArrayAdapter; |
| import android.widget.ListView; |
| import android.widget.Toast; |
| |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| |
| /** |
| * Test for USB accessories. The test activity interacts with a cts-usb-accessory program that |
| * acts as an accessory by exchanging a series of messages. |
| */ |
| public class UsbAccessoryTestActivity extends PassFailButtons.Activity { |
| |
| private static final String TAG = "UsbAccessoryTest"; |
| |
| private static final int FILE_DESCRIPTOR_PROBLEM_DIALOG_ID = 1; |
| private static final int STATE_START = 0; |
| private static final int STATE_CONNECTED = 1; |
| private static final int STATE_WAITING_FOR_RECONNECT = 2; |
| private static final int STATE_RECONNECTED = 3; |
| private static final int STATE_PASSED = 4; |
| |
| private static final String ACTION_USB_PERMISSION = |
| "com.android.cts.verifier.usb.USB_PERMISSION"; |
| |
| private ArrayAdapter<String> mReceivedMessagesAdapter; |
| private ArrayAdapter<String> mSentMessagesAdapter; |
| private MessageHandler mHandler; |
| private Handler mMainHandler; |
| |
| private UsbManager mUsbManager; |
| private PendingIntent mPermissionIntent; |
| private boolean mPermissionRequestPending; |
| private UsbReceiver mUsbReceiver; |
| private int mState = STATE_START; |
| private AlertDialog mDisconnectDialog; |
| private AlertDialog mConnectDialog; |
| |
| private UsbAccessory mAccessory; |
| private ParcelFileDescriptor mFileDescriptor; |
| |
| private Runnable mTimeoutRunnable = new Runnable() { |
| @Override |
| public void run() { |
| Toast.makeText(UsbAccessoryTestActivity.this, |
| R.string.usb_reconnect_timeout, Toast.LENGTH_SHORT).show(); |
| TestResult.setFailedResult(UsbAccessoryTestActivity.this, getTestId(), getTestDetails()); |
| } |
| }; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| Log.d(TAG, "onCreate"); |
| // Test success only works properly if launched from TestListActivity |
| String action = getIntent().getAction(); |
| if (ACTION_USB_PERMISSION.equals(action) |
| || UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) { |
| finish(); |
| return; |
| } |
| |
| setContentView(R.layout.usb_main); |
| setInfoResources(R.string.usb_accessory_test, R.string.usb_accessory_test_info, -1); |
| setPassFailButtonClickListeners(); |
| |
| // Don't allow a test pass until the accessory and the Android device exchange messages... |
| getPassButton().setEnabled(false); |
| |
| if (!hasUsbAccessorySupport()) { |
| showNoUsbAccessoryDialog(); |
| } |
| |
| mReceivedMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); |
| mSentMessagesAdapter = new ArrayAdapter<String>(this, R.layout.usb_message_row); |
| mHandler = new MessageHandler(); |
| |
| mUsbManager = (UsbManager) getSystemService(USB_SERVICE); |
| mPermissionIntent = PendingIntent.getBroadcast(this, 0, |
| new Intent(ACTION_USB_PERMISSION), 0); |
| |
| mUsbReceiver = new UsbReceiver(); |
| IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); |
| filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); |
| registerReceiver(mUsbReceiver, filter); |
| |
| setupListViews(); |
| |
| AlertDialog.Builder builder = new AlertDialog.Builder(this) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setTitle(R.string.usb_reconnect_title) |
| .setCancelable(false) |
| .setNegativeButton(R.string.usb_reconnect_abort, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| setTestResultAndFinish(false); |
| } |
| }); |
| mConnectDialog = builder |
| .setMessage(R.string.usb_connect_message) |
| .create(); |
| mDisconnectDialog = builder |
| .setMessage(R.string.usb_disconnect_message) |
| .create(); |
| |
| mMainHandler = new Handler(Looper.getMainLooper()); |
| } |
| |
| private boolean hasUsbAccessorySupport() { |
| return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); |
| } |
| |
| private void showNoUsbAccessoryDialog() { |
| new AlertDialog.Builder(this) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setTitle(R.string.usb_not_available_title) |
| .setMessage(R.string.usb_not_available_message) |
| .setCancelable(false) |
| .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| finish(); |
| } |
| }) |
| .show(); |
| } |
| |
| private void setupListViews() { |
| ListView sentMessages = (ListView) findViewById(R.id.usb_sent_messages); |
| ListView receivedMessages = (ListView) findViewById(R.id.usb_received_messages); |
| |
| View emptySentView = findViewById(R.id.usb_empty_sent_messages); |
| View emptyReceivedView = findViewById(R.id.usb_empty_received_messages); |
| sentMessages.setEmptyView(emptySentView); |
| receivedMessages.setEmptyView(emptyReceivedView); |
| |
| receivedMessages.setAdapter(mReceivedMessagesAdapter); |
| sentMessages.setAdapter(mSentMessagesAdapter); |
| } |
| |
| class UsbReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Log.d(TAG, "Received broadcast: intent=" + intent); |
| if (ACTION_USB_PERMISSION.equals(intent.getAction())) { |
| if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { |
| UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); |
| openAccessory(accessory); |
| } else { |
| Log.i(TAG, "Permission denied..."); |
| } |
| mPermissionRequestPending = false; |
| } else if (mState == STATE_WAITING_FOR_RECONNECT && |
| UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(intent.getAction())) { |
| UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); |
| if (accessory.equals(mAccessory)) { |
| closeAccessory(); |
| mDisconnectDialog.dismiss(); |
| mConnectDialog.show(); |
| mMainHandler.postDelayed(mTimeoutRunnable, 10000 /* 10 seconds */); |
| } |
| } |
| } |
| } |
| |
| public void onNewIntent(Intent intent) { |
| super.onNewIntent(intent); |
| Log.d(TAG, "onNewIntent: state=" + mState + ", intent=" + intent); |
| if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) { |
| UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); |
| openAccessory(accessory); |
| } |
| } |
| |
| private void openAccessory(UsbAccessory accessory) { |
| mAccessory = accessory; |
| mFileDescriptor = mUsbManager.openAccessory(accessory); |
| if (mState == STATE_START) { |
| setState(STATE_CONNECTED); |
| } else if (mState == STATE_WAITING_FOR_RECONNECT) { |
| setState(STATE_RECONNECTED); |
| mConnectDialog.dismiss(); |
| } |
| if (mFileDescriptor != null) { |
| FileDescriptor fileDescriptor = mFileDescriptor.getFileDescriptor(); |
| FileInputStream inputStream = new FileInputStream(fileDescriptor); |
| FileOutputStream outputStream = new FileOutputStream(fileDescriptor); |
| new MessageThread(inputStream, outputStream, mHandler).start(); |
| } else { |
| showDialog(FILE_DESCRIPTOR_PROBLEM_DIALOG_ID); |
| } |
| } |
| |
| private void closeAccessory() { |
| mAccessory = null; |
| if (mFileDescriptor != null) { |
| try { |
| mFileDescriptor.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Exception while closing file descriptor", e); |
| } finally { |
| mFileDescriptor = null; |
| } |
| } |
| } |
| |
| static class MessageThread extends Thread { |
| |
| private final InputStream mInputStream; |
| |
| private final OutputStream mOutputStream; |
| |
| private final MessageHandler mHandler; |
| |
| private int mNextMessageNumber = 0; |
| |
| MessageThread(InputStream inputStream, OutputStream outputStream, MessageHandler handler) { |
| this.mInputStream = inputStream; |
| this.mOutputStream = outputStream; |
| this.mHandler = handler; |
| } |
| |
| @Override |
| public void run() { |
| mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_STARTING); |
| |
| try { |
| // Wait a bit or else the messages can appear to quick and be confusing... |
| Thread.sleep(2000); |
| sendMessage(); |
| |
| // Wait for response and send message acks... |
| int numRead = 0; |
| byte[] buffer = new byte[16384]; |
| boolean done = false; |
| while (numRead >= 0 && !done) { |
| numRead = mInputStream.read(buffer); |
| if (numRead > 0) { |
| done = handleReceivedMessage(buffer, numRead); |
| } |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "Exception while reading from input stream", e); |
| mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "Exception while reading from input stream", e); |
| mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_EXCEPTION); |
| } |
| mHandler.sendEmptyMessage(MessageHandler.MESSAGE_THREAD_ENDING); |
| } |
| |
| private boolean handleReceivedMessage(byte[] buffer, int numRead) throws IOException { |
| // TODO: Check the contents of the message? |
| String text = new String(buffer, 0, numRead).trim(); |
| mHandler.sendReceivedMessage(text); |
| |
| // Send back a response.. |
| if (mNextMessageNumber <= 10) { |
| sendMessage(); |
| return false; |
| } else { |
| mHandler.sendEmptyMessage(MessageHandler.STAGE_PASSED); |
| return true; |
| } |
| } |
| |
| private void sendMessage() throws IOException { |
| String text = "Message from Android device #" + mNextMessageNumber++; |
| mOutputStream.write(text.getBytes()); |
| mHandler.sendSentMessage(text); |
| } |
| } |
| |
| class MessageHandler extends Handler { |
| |
| static final int RECEIVED_MESSAGE = 1; |
| |
| static final int SENT_MESSAGE = 2; |
| |
| static final int MESSAGE_THREAD_STARTING = 3; |
| |
| static final int MESSAGE_THREAD_EXCEPTION = 4; |
| |
| static final int MESSAGE_THREAD_ENDING = 5; |
| |
| static final int STAGE_PASSED = 6; |
| |
| @Override |
| public void handleMessage(Message msg) { |
| super.handleMessage(msg); |
| switch (msg.what) { |
| case RECEIVED_MESSAGE: |
| mReceivedMessagesAdapter.add((String) msg.obj); |
| break; |
| |
| case SENT_MESSAGE: |
| mSentMessagesAdapter.add((String) msg.obj); |
| break; |
| |
| case MESSAGE_THREAD_STARTING: |
| showToast(R.string.usb_message_thread_started); |
| break; |
| |
| case MESSAGE_THREAD_EXCEPTION: |
| showToast(R.string.usb_message_thread_exception); |
| break; |
| |
| case MESSAGE_THREAD_ENDING: |
| showToast(R.string.usb_message_thread_ended); |
| break; |
| |
| case STAGE_PASSED: |
| if (mState == STATE_RECONNECTED) { |
| showToast(R.string.usb_test_passed); |
| getPassButton().setEnabled(true); |
| setState(STATE_PASSED); |
| mMainHandler.removeCallbacks(mTimeoutRunnable); |
| } else if (mState == STATE_CONNECTED) { |
| mDisconnectDialog.show(); |
| setState(STATE_WAITING_FOR_RECONNECT); |
| } |
| break; |
| |
| default: |
| throw new IllegalArgumentException("Bad message type: " + msg.what); |
| } |
| } |
| |
| private void showToast(int messageId) { |
| Toast.makeText(UsbAccessoryTestActivity.this, messageId, Toast.LENGTH_SHORT).show(); |
| } |
| |
| void sendReceivedMessage(String text) { |
| Message message = Message.obtain(this, RECEIVED_MESSAGE); |
| message.obj = text; |
| sendMessage(message); |
| } |
| |
| void sendSentMessage(String text) { |
| Message message = Message.obtain(this, SENT_MESSAGE); |
| message.obj = text; |
| sendMessage(message); |
| } |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| Log.d(TAG, "onResume: state=" + stateToString(mState)); |
| if (mState == STATE_START) { |
| UsbAccessory[] accessories = mUsbManager.getAccessoryList(); |
| UsbAccessory accessory = accessories != null && accessories.length > 0 |
| ? accessories[0] |
| : null; |
| if (accessory != null) { |
| if (mUsbManager.hasPermission(accessory)) { |
| openAccessory(accessory); |
| } else { |
| if (!mPermissionRequestPending) { |
| mUsbManager.requestPermission(accessory, mPermissionIntent); |
| mPermissionRequestPending = true; |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| Log.d(TAG, "onPause: state=" + stateToString(mState)); |
| } |
| |
| @Override |
| protected void onStop() { |
| super.onStop(); |
| Log.d(TAG, "onStop: state=" + stateToString(mState)); |
| closeAccessory(); |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| setContentView(R.layout.usb_main); |
| setupListViews(); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(int id, Bundle args) { |
| switch (id) { |
| case FILE_DESCRIPTOR_PROBLEM_DIALOG_ID: |
| return new AlertDialog.Builder(this) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setTitle(R.string.usb_accessory_test) |
| .setMessage(R.string.usb_file_descriptor_error) |
| .create(); |
| |
| default: |
| return super.onCreateDialog(id, args); |
| } |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| Log.d(TAG, "onDestroy"); |
| if (mUsbReceiver != null) { |
| unregisterReceiver(mUsbReceiver); |
| } |
| } |
| |
| private void setState(int newState) { |
| Log.d(TAG, "Transition: " + stateToString(mState) + " -> " + stateToString(newState)); |
| mState = newState; |
| } |
| |
| |
| private static String stateToString(int state) { |
| switch (state) { |
| case STATE_START: return "START"; |
| case STATE_CONNECTED: return "CONNECTED"; |
| case STATE_WAITING_FOR_RECONNECT: return "WAITING_FOR_RECONNECT"; |
| case STATE_RECONNECTED: return "RECONNECTED"; |
| case STATE_PASSED: return "PASSED"; |
| default: return "UNKNOWN"; |
| } |
| } |
| |
| } |