/* * Copyright (C) 2013 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.accessorydisplay.sink; import com.android.accessorydisplay.common.Logger; import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; import android.media.MediaFormat; import android.os.Bundle; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.TextView; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.Map; public class SinkActivity extends Activity { private static final String TAG = "SinkActivity"; private static final String ACTION_USB_DEVICE_PERMISSION = "com.android.accessorydisplay.sink.ACTION_USB_DEVICE_PERMISSION"; private static final String MANUFACTURER = "Android"; private static final String MODEL = "Accessory Display"; private static final String DESCRIPTION = "Accessory Display Sink Test Application"; private static final String VERSION = "1.0"; private static final String URI = "http://www.android.com/"; private static final String SERIAL = "0000000012345678"; private static final int MULTITOUCH_DEVICE_ID = 0; private static final int MULTITOUCH_REPORT_ID = 1; private static final int MULTITOUCH_MAX_CONTACTS = 1; private UsbManager mUsbManager; private DeviceReceiver mReceiver; private TextView mLogTextView; private TextView mFpsTextView; private SurfaceView mSurfaceView; private Logger mLogger; private boolean mConnected; private int mProtocolVersion; private UsbDevice mDevice; private UsbInterface mAccessoryInterface; private UsbDeviceConnection mAccessoryConnection; private UsbEndpoint mControlEndpoint; private UsbAccessoryBulkTransport mTransport; private boolean mAttached; private DisplaySinkService mDisplaySinkService; private final ByteBuffer mHidBuffer = ByteBuffer.allocate(4096); private UsbHid.Multitouch mMultitouch; private boolean mMultitouchEnabled; private UsbHid.Multitouch.Contact[] mMultitouchContacts; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE); setContentView(R.layout.sink_activity); mLogTextView = (TextView) findViewById(R.id.logTextView); mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance()); mLogger = new TextLogger(); mFpsTextView = (TextView) findViewById(R.id.fpsTextView); mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView); mSurfaceView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { sendHidTouch(event); return true; } }); mLogger.log("Waiting for accessory display source to be attached to USB..."); IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(ACTION_USB_DEVICE_PERMISSION); mReceiver = new DeviceReceiver(); registerReceiver(mReceiver, filter); Intent intent = getIntent(); if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { onDeviceAttached(device); } } else { Map devices = mUsbManager.getDeviceList(); if (devices != null) { for (UsbDevice device : devices.values()) { onDeviceAttached(device); } } } } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mReceiver); } private void onDeviceAttached(UsbDevice device) { mLogger.log("USB device attached: " + device); if (!mConnected) { connect(device); } } private void onDeviceDetached(UsbDevice device) { mLogger.log("USB device detached: " + device); if (mConnected && device.equals(mDevice)) { disconnect(); } } private void connect(UsbDevice device) { if (mConnected) { disconnect(); } // Check whether we have permission to access the device. if (!mUsbManager.hasPermission(device)) { mLogger.log("Prompting the user for access to the device."); Intent intent = new Intent(ACTION_USB_DEVICE_PERMISSION); intent.setPackage(getPackageName()); PendingIntent pendingIntent = PendingIntent.getBroadcast( this, 0, intent, PendingIntent.FLAG_ONE_SHOT); mUsbManager.requestPermission(device, pendingIntent); return; } // Claim the device. UsbDeviceConnection conn = mUsbManager.openDevice(device); if (conn == null) { mLogger.logError("Could not obtain device connection."); return; } UsbInterface iface = device.getInterface(0); UsbEndpoint controlEndpoint = iface.getEndpoint(0); if (!conn.claimInterface(iface, true)) { mLogger.logError("Could not claim interface."); return; } try { // If already in accessory mode, then connect to the device. if (isAccessory(device)) { mLogger.log("Connecting to accessory..."); int protocolVersion = getProtocol(conn); if (protocolVersion < 1) { mLogger.logError("Device does not support accessory protocol."); return; } mLogger.log("Protocol version: " + protocolVersion); // Setup bulk endpoints. UsbEndpoint bulkIn = null; UsbEndpoint bulkOut = null; for (int i = 0; i < iface.getEndpointCount(); i++) { UsbEndpoint ep = iface.getEndpoint(i); if (ep.getDirection() == UsbConstants.USB_DIR_IN) { if (bulkIn == null) { mLogger.log(String.format("Bulk IN endpoint: %d", i)); bulkIn = ep; } } else { if (bulkOut == null) { mLogger.log(String.format("Bulk OUT endpoint: %d", i)); bulkOut = ep; } } } if (bulkIn == null || bulkOut == null) { mLogger.logError("Unable to find bulk endpoints"); return; } mLogger.log("Connected"); mConnected = true; mDevice = device; mProtocolVersion = protocolVersion; mAccessoryInterface = iface; mAccessoryConnection = conn; mControlEndpoint = controlEndpoint; mTransport = new UsbAccessoryBulkTransport(mLogger, conn, bulkIn, bulkOut); if (mProtocolVersion >= 2) { registerHid(); } startServices(); mTransport.startReading(); return; } // Do accessory negotiation. mLogger.log("Attempting to switch device to accessory mode..."); // Send get protocol. int protocolVersion = getProtocol(conn); if (protocolVersion < 1) { mLogger.logError("Device does not support accessory protocol."); return; } mLogger.log("Protocol version: " + protocolVersion); // Send identifying strings. sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MANUFACTURER, MANUFACTURER); sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MODEL, MODEL); sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_DESCRIPTION, DESCRIPTION); sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_VERSION, VERSION); sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_URI, URI); sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_SERIAL, SERIAL); // Send start. // The device should re-enumerate as an accessory. mLogger.log("Sending accessory start request."); int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_START, 0, 0, null, 0, 10000); if (len != 0) { mLogger.logError("Device refused to switch to accessory mode."); } else { mLogger.log("Waiting for device to re-enumerate..."); } } finally { if (!mConnected) { conn.releaseInterface(iface); } } } private void disconnect() { mLogger.log("Disconnecting from device: " + mDevice); stopServices(); unregisterHid(); mLogger.log("Disconnected."); mConnected = false; mDevice = null; mAccessoryConnection = null; mAccessoryInterface = null; mControlEndpoint = null; if (mTransport != null) { mTransport.close(); mTransport = null; } } private void registerHid() { mLogger.log("Registering HID multitouch device."); mMultitouch = new UsbHid.Multitouch(MULTITOUCH_REPORT_ID, MULTITOUCH_MAX_CONTACTS, mSurfaceView.getWidth(), mSurfaceView.getHeight()); mHidBuffer.clear(); mMultitouch.generateDescriptor(mHidBuffer); mHidBuffer.flip(); mLogger.log("HID descriptor size: " + mHidBuffer.limit()); mLogger.log("HID report size: " + mMultitouch.getReportSize()); final int maxPacketSize = mControlEndpoint.getMaxPacketSize(); mLogger.log("Control endpoint max packet size: " + maxPacketSize); if (mMultitouch.getReportSize() > maxPacketSize) { mLogger.logError("HID report is too big for this accessory."); return; } int len = mAccessoryConnection.controlTransfer( UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_REGISTER_HID, MULTITOUCH_DEVICE_ID, mHidBuffer.limit(), null, 0, 10000); if (len != 0) { mLogger.logError("Device rejected ACCESSORY_REGISTER_HID request."); return; } while (mHidBuffer.hasRemaining()) { int position = mHidBuffer.position(); int count = Math.min(mHidBuffer.remaining(), maxPacketSize); len = mAccessoryConnection.controlTransfer( UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_SET_HID_REPORT_DESC, MULTITOUCH_DEVICE_ID, 0, mHidBuffer.array(), position, count, 10000); if (len != count) { mLogger.logError("Device rejected ACCESSORY_SET_HID_REPORT_DESC request."); return; } mHidBuffer.position(position + count); } mLogger.log("HID device registered."); mMultitouchEnabled = true; if (mMultitouchContacts == null) { mMultitouchContacts = new UsbHid.Multitouch.Contact[MULTITOUCH_MAX_CONTACTS]; for (int i = 0; i < MULTITOUCH_MAX_CONTACTS; i++) { mMultitouchContacts[i] = new UsbHid.Multitouch.Contact(); } } } private void unregisterHid() { mMultitouch = null; mMultitouchContacts = null; mMultitouchEnabled = false; } private void sendHidTouch(MotionEvent event) { if (mMultitouchEnabled) { mLogger.log("Sending touch event: " + event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: { final int pointerCount = Math.min(MULTITOUCH_MAX_CONTACTS, event.getPointerCount()); final int historySize = event.getHistorySize(); for (int p = 0; p < pointerCount; p++) { mMultitouchContacts[p].id = event.getPointerId(p); } for (int h = 0; h < historySize; h++) { for (int p = 0; p < pointerCount; p++) { mMultitouchContacts[p].x = (int)event.getHistoricalX(p, h); mMultitouchContacts[p].y = (int)event.getHistoricalY(p, h); } sendHidTouchReport(pointerCount); } for (int p = 0; p < pointerCount; p++) { mMultitouchContacts[p].x = (int)event.getX(p); mMultitouchContacts[p].y = (int)event.getY(p); } sendHidTouchReport(pointerCount); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: sendHidTouchReport(0); break; } } } private void sendHidTouchReport(int contactCount) { mHidBuffer.clear(); mMultitouch.generateReport(mHidBuffer, mMultitouchContacts, contactCount); mHidBuffer.flip(); int count = mHidBuffer.limit(); int len = mAccessoryConnection.controlTransfer( UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_SEND_HID_EVENT, MULTITOUCH_DEVICE_ID, 0, mHidBuffer.array(), 0, count, 10000); if (len != count) { mLogger.logError("Device rejected ACCESSORY_SEND_HID_EVENT request."); return; } } private void startServices() { mDisplaySinkService = new DisplaySinkService(this, mTransport, getResources().getConfiguration().densityDpi); mDisplaySinkService.start(); if (mAttached) { mDisplaySinkService.setSurfaceView(mSurfaceView); } } private void stopServices() { if (mDisplaySinkService != null) { mDisplaySinkService.stop(); mDisplaySinkService = null; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mAttached = true; if (mDisplaySinkService != null) { mDisplaySinkService.setSurfaceView(mSurfaceView); } } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); mAttached = false; if (mDisplaySinkService != null) { mDisplaySinkService.setSurfaceView(null); } } private int getProtocol(UsbDeviceConnection conn) { byte buffer[] = new byte[2]; int len = conn.controlTransfer( UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, 10000); if (len != 2) { return -1; } return (buffer[1] << 8) | buffer[0]; } private void sendString(UsbDeviceConnection conn, int index, String string) { byte[] buffer = (string + "\0").getBytes(); int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, UsbAccessoryConstants.ACCESSORY_SEND_STRING, 0, index, buffer, buffer.length, 10000); if (len != buffer.length) { mLogger.logError("Failed to send string " + index + ": \"" + string + "\""); } else { mLogger.log("Sent string " + index + ": \"" + string + "\""); } } private static boolean isAccessory(UsbDevice device) { final int vid = device.getVendorId(); final int pid = device.getProductId(); return vid == UsbAccessoryConstants.USB_ACCESSORY_VENDOR_ID && (pid == UsbAccessoryConstants.USB_ACCESSORY_PRODUCT_ID || pid == UsbAccessoryConstants.USB_ACCESSORY_ADB_PRODUCT_ID); } class TextLogger extends Logger { @Override public void log(final String message) { Log.d(TAG, message); mLogTextView.post(new Runnable() { @Override public void run() { mLogTextView.append(message); mLogTextView.append("\n"); } }); } } class DeviceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { onDeviceAttached(device); } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { onDeviceDetached(device); } else if (action.equals(ACTION_USB_DEVICE_PERMISSION)) { if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { mLogger.log("Device permission granted: " + device); onDeviceAttached(device); } else { mLogger.logError("Device permission denied: " + device); } } } } } }