blob: 6fe2cfbcd8f40c422004fbf59543987e87ab3b87 [file] [log] [blame]
/*
* 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.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
onDeviceAttached(device);
}
} else {
Map<String, UsbDevice> 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.<UsbDevice>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);
}
}
}
}
}
}