Home | History | Annotate | Download | only in sink
      1 /*
      2  * Copyright (C) 2013 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.accessorydisplay.sink;
     18 
     19 import com.android.accessorydisplay.common.Logger;
     20 
     21 import android.app.Activity;
     22 import android.app.PendingIntent;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.hardware.usb.UsbConstants;
     28 import android.hardware.usb.UsbDevice;
     29 import android.hardware.usb.UsbDeviceConnection;
     30 import android.hardware.usb.UsbEndpoint;
     31 import android.hardware.usb.UsbInterface;
     32 import android.hardware.usb.UsbManager;
     33 import android.media.MediaCodec;
     34 import android.media.MediaCodec.BufferInfo;
     35 import android.media.MediaFormat;
     36 import android.os.Bundle;
     37 import android.text.method.ScrollingMovementMethod;
     38 import android.util.Log;
     39 import android.view.MotionEvent;
     40 import android.view.Surface;
     41 import android.view.SurfaceHolder;
     42 import android.view.SurfaceView;
     43 import android.view.View;
     44 import android.widget.TextView;
     45 
     46 import java.nio.ByteBuffer;
     47 import java.util.LinkedList;
     48 import java.util.Map;
     49 
     50 public class SinkActivity extends Activity {
     51     private static final String TAG = "SinkActivity";
     52 
     53     private static final String ACTION_USB_DEVICE_PERMISSION =
     54             "com.android.accessorydisplay.sink.ACTION_USB_DEVICE_PERMISSION";
     55 
     56     private static final String MANUFACTURER = "Android";
     57     private static final String MODEL = "Accessory Display";
     58     private static final String DESCRIPTION = "Accessory Display Sink Test Application";
     59     private static final String VERSION = "1.0";
     60     private static final String URI = "http://www.android.com/";
     61     private static final String SERIAL = "0000000012345678";
     62 
     63     private static final int MULTITOUCH_DEVICE_ID = 0;
     64     private static final int MULTITOUCH_REPORT_ID = 1;
     65     private static final int MULTITOUCH_MAX_CONTACTS = 1;
     66 
     67     private UsbManager mUsbManager;
     68     private DeviceReceiver mReceiver;
     69     private TextView mLogTextView;
     70     private TextView mFpsTextView;
     71     private SurfaceView mSurfaceView;
     72     private Logger mLogger;
     73 
     74     private boolean mConnected;
     75     private int mProtocolVersion;
     76     private UsbDevice mDevice;
     77     private UsbInterface mAccessoryInterface;
     78     private UsbDeviceConnection mAccessoryConnection;
     79     private UsbEndpoint mControlEndpoint;
     80     private UsbAccessoryBulkTransport mTransport;
     81 
     82     private boolean mAttached;
     83     private DisplaySinkService mDisplaySinkService;
     84 
     85     private final ByteBuffer mHidBuffer = ByteBuffer.allocate(4096);
     86     private UsbHid.Multitouch mMultitouch;
     87     private boolean mMultitouchEnabled;
     88     private UsbHid.Multitouch.Contact[] mMultitouchContacts;
     89 
     90     @Override
     91     protected void onCreate(Bundle savedInstanceState) {
     92         super.onCreate(savedInstanceState);
     93 
     94         mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
     95 
     96         setContentView(R.layout.sink_activity);
     97 
     98         mLogTextView = findViewById(R.id.logTextView);
     99         mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance());
    100         mLogger = new TextLogger();
    101 
    102         mFpsTextView = findViewById(R.id.fpsTextView);
    103 
    104         mSurfaceView = findViewById(R.id.surfaceView);
    105         mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
    106             @Override
    107             public boolean onTouch(View v, MotionEvent event) {
    108                 sendHidTouch(event);
    109                 return true;
    110             }
    111         });
    112 
    113         mLogger.log("Waiting for accessory display source to be attached to USB...");
    114 
    115         IntentFilter filter = new IntentFilter();
    116         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
    117         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
    118         filter.addAction(ACTION_USB_DEVICE_PERMISSION);
    119         mReceiver = new DeviceReceiver();
    120         registerReceiver(mReceiver, filter);
    121 
    122         Intent intent = getIntent();
    123         if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
    124             UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
    125             if (device != null) {
    126                 onDeviceAttached(device);
    127             }
    128         } else {
    129             Map<String, UsbDevice> devices = mUsbManager.getDeviceList();
    130             if (devices != null) {
    131                 for (UsbDevice device : devices.values()) {
    132                     onDeviceAttached(device);
    133                 }
    134             }
    135         }
    136     }
    137 
    138     @Override
    139     protected void onDestroy() {
    140         super.onDestroy();
    141 
    142         unregisterReceiver(mReceiver);
    143     }
    144 
    145     private void onDeviceAttached(UsbDevice device) {
    146         mLogger.log("USB device attached: " + device);
    147         if (!mConnected) {
    148             connect(device);
    149         }
    150     }
    151 
    152     private void onDeviceDetached(UsbDevice device) {
    153         mLogger.log("USB device detached: " + device);
    154         if (mConnected && device.equals(mDevice)) {
    155             disconnect();
    156         }
    157     }
    158 
    159     private void connect(UsbDevice device) {
    160         if (mConnected) {
    161             disconnect();
    162         }
    163 
    164         // Check whether we have permission to access the device.
    165         if (!mUsbManager.hasPermission(device)) {
    166             mLogger.log("Prompting the user for access to the device.");
    167             Intent intent = new Intent(ACTION_USB_DEVICE_PERMISSION);
    168             intent.setPackage(getPackageName());
    169             PendingIntent pendingIntent = PendingIntent.getBroadcast(
    170                     this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    171             mUsbManager.requestPermission(device, pendingIntent);
    172             return;
    173         }
    174 
    175         // Claim the device.
    176         UsbDeviceConnection conn = mUsbManager.openDevice(device);
    177         if (conn == null) {
    178             mLogger.logError("Could not obtain device connection.");
    179             return;
    180         }
    181         UsbInterface iface = device.getInterface(0);
    182         UsbEndpoint controlEndpoint = iface.getEndpoint(0);
    183         if (!conn.claimInterface(iface, true)) {
    184             mLogger.logError("Could not claim interface.");
    185             return;
    186         }
    187         try {
    188             // If already in accessory mode, then connect to the device.
    189             if (isAccessory(device)) {
    190                 mLogger.log("Connecting to accessory...");
    191 
    192                 int protocolVersion = getProtocol(conn);
    193                 if (protocolVersion < 1) {
    194                     mLogger.logError("Device does not support accessory protocol.");
    195                     return;
    196                 }
    197                 mLogger.log("Protocol version: " + protocolVersion);
    198 
    199                 // Setup bulk endpoints.
    200                 UsbEndpoint bulkIn = null;
    201                 UsbEndpoint bulkOut = null;
    202                 for (int i = 0; i < iface.getEndpointCount(); i++) {
    203                     UsbEndpoint ep = iface.getEndpoint(i);
    204                     if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
    205                         if (bulkIn == null) {
    206                             mLogger.log(String.format("Bulk IN endpoint: %d", i));
    207                             bulkIn = ep;
    208                         }
    209                     } else {
    210                         if (bulkOut == null) {
    211                             mLogger.log(String.format("Bulk OUT endpoint: %d", i));
    212                             bulkOut = ep;
    213                         }
    214                     }
    215                 }
    216                 if (bulkIn == null || bulkOut == null) {
    217                     mLogger.logError("Unable to find bulk endpoints");
    218                     return;
    219                 }
    220 
    221                 mLogger.log("Connected");
    222                 mConnected = true;
    223                 mDevice = device;
    224                 mProtocolVersion = protocolVersion;
    225                 mAccessoryInterface = iface;
    226                 mAccessoryConnection = conn;
    227                 mControlEndpoint = controlEndpoint;
    228                 mTransport = new UsbAccessoryBulkTransport(mLogger, conn, bulkIn, bulkOut);
    229                 if (mProtocolVersion >= 2) {
    230                     registerHid();
    231                 }
    232                 startServices();
    233                 mTransport.startReading();
    234                 return;
    235             }
    236 
    237             // Do accessory negotiation.
    238             mLogger.log("Attempting to switch device to accessory mode...");
    239 
    240             // Send get protocol.
    241             int protocolVersion = getProtocol(conn);
    242             if (protocolVersion < 1) {
    243                 mLogger.logError("Device does not support accessory protocol.");
    244                 return;
    245             }
    246             mLogger.log("Protocol version: " + protocolVersion);
    247 
    248             // Send identifying strings.
    249             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MANUFACTURER, MANUFACTURER);
    250             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MODEL, MODEL);
    251             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_DESCRIPTION, DESCRIPTION);
    252             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_VERSION, VERSION);
    253             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_URI, URI);
    254             sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_SERIAL, SERIAL);
    255 
    256             // Send start.
    257             // The device should re-enumerate as an accessory.
    258             mLogger.log("Sending accessory start request.");
    259             int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
    260                     UsbAccessoryConstants.ACCESSORY_START, 0, 0, null, 0, 10000);
    261             if (len != 0) {
    262                 mLogger.logError("Device refused to switch to accessory mode.");
    263             } else {
    264                 mLogger.log("Waiting for device to re-enumerate...");
    265             }
    266         } finally {
    267             if (!mConnected) {
    268                 conn.releaseInterface(iface);
    269             }
    270         }
    271     }
    272 
    273     private void disconnect() {
    274         mLogger.log("Disconnecting from device: " + mDevice);
    275         stopServices();
    276         unregisterHid();
    277 
    278         mLogger.log("Disconnected.");
    279         mConnected = false;
    280         mDevice = null;
    281         mAccessoryConnection = null;
    282         mAccessoryInterface = null;
    283         mControlEndpoint = null;
    284         if (mTransport != null) {
    285             mTransport.close();
    286             mTransport = null;
    287         }
    288     }
    289 
    290     private void registerHid() {
    291         mLogger.log("Registering HID multitouch device.");
    292 
    293         mMultitouch = new UsbHid.Multitouch(MULTITOUCH_REPORT_ID, MULTITOUCH_MAX_CONTACTS,
    294                 mSurfaceView.getWidth(), mSurfaceView.getHeight());
    295 
    296         mHidBuffer.clear();
    297         mMultitouch.generateDescriptor(mHidBuffer);
    298         mHidBuffer.flip();
    299 
    300         mLogger.log("HID descriptor size: " + mHidBuffer.limit());
    301         mLogger.log("HID report size: " + mMultitouch.getReportSize());
    302 
    303         final int maxPacketSize = mControlEndpoint.getMaxPacketSize();
    304         mLogger.log("Control endpoint max packet size: " + maxPacketSize);
    305         if (mMultitouch.getReportSize() > maxPacketSize) {
    306             mLogger.logError("HID report is too big for this accessory.");
    307             return;
    308         }
    309 
    310         int len = mAccessoryConnection.controlTransfer(
    311                 UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
    312                 UsbAccessoryConstants.ACCESSORY_REGISTER_HID,
    313                 MULTITOUCH_DEVICE_ID, mHidBuffer.limit(), null, 0, 10000);
    314         if (len != 0) {
    315             mLogger.logError("Device rejected ACCESSORY_REGISTER_HID request.");
    316             return;
    317         }
    318 
    319         while (mHidBuffer.hasRemaining()) {
    320             int position = mHidBuffer.position();
    321             int count = Math.min(mHidBuffer.remaining(), maxPacketSize);
    322             len = mAccessoryConnection.controlTransfer(
    323                     UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
    324                     UsbAccessoryConstants.ACCESSORY_SET_HID_REPORT_DESC,
    325                     MULTITOUCH_DEVICE_ID, 0,
    326                     mHidBuffer.array(), position, count, 10000);
    327             if (len != count) {
    328                 mLogger.logError("Device rejected ACCESSORY_SET_HID_REPORT_DESC request.");
    329                 return;
    330             }
    331             mHidBuffer.position(position + count);
    332         }
    333 
    334         mLogger.log("HID device registered.");
    335 
    336         mMultitouchEnabled = true;
    337         if (mMultitouchContacts == null) {
    338             mMultitouchContacts = new UsbHid.Multitouch.Contact[MULTITOUCH_MAX_CONTACTS];
    339             for (int i = 0; i < MULTITOUCH_MAX_CONTACTS; i++) {
    340                 mMultitouchContacts[i] = new UsbHid.Multitouch.Contact();
    341             }
    342         }
    343     }
    344 
    345     private void unregisterHid() {
    346         mMultitouch = null;
    347         mMultitouchContacts = null;
    348         mMultitouchEnabled = false;
    349     }
    350 
    351     private void sendHidTouch(MotionEvent event) {
    352         if (mMultitouchEnabled) {
    353             mLogger.log("Sending touch event: " + event);
    354 
    355             switch (event.getActionMasked()) {
    356                 case MotionEvent.ACTION_DOWN:
    357                 case MotionEvent.ACTION_MOVE: {
    358                     final int pointerCount =
    359                             Math.min(MULTITOUCH_MAX_CONTACTS, event.getPointerCount());
    360                     final int historySize = event.getHistorySize();
    361                     for (int p = 0; p < pointerCount; p++) {
    362                         mMultitouchContacts[p].id = event.getPointerId(p);
    363                     }
    364                     for (int h = 0; h < historySize; h++) {
    365                         for (int p = 0; p < pointerCount; p++) {
    366                             mMultitouchContacts[p].x = (int)event.getHistoricalX(p, h);
    367                             mMultitouchContacts[p].y = (int)event.getHistoricalY(p, h);
    368                         }
    369                         sendHidTouchReport(pointerCount);
    370                     }
    371                     for (int p = 0; p < pointerCount; p++) {
    372                         mMultitouchContacts[p].x = (int)event.getX(p);
    373                         mMultitouchContacts[p].y = (int)event.getY(p);
    374                     }
    375                     sendHidTouchReport(pointerCount);
    376                     break;
    377                 }
    378 
    379                 case MotionEvent.ACTION_CANCEL:
    380                 case MotionEvent.ACTION_UP:
    381                     sendHidTouchReport(0);
    382                     break;
    383             }
    384         }
    385     }
    386 
    387     private void sendHidTouchReport(int contactCount) {
    388         mHidBuffer.clear();
    389         mMultitouch.generateReport(mHidBuffer, mMultitouchContacts, contactCount);
    390         mHidBuffer.flip();
    391 
    392         int count = mHidBuffer.limit();
    393         int len = mAccessoryConnection.controlTransfer(
    394                 UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
    395                 UsbAccessoryConstants.ACCESSORY_SEND_HID_EVENT,
    396                 MULTITOUCH_DEVICE_ID, 0,
    397                 mHidBuffer.array(), 0, count, 10000);
    398         if (len != count) {
    399             mLogger.logError("Device rejected ACCESSORY_SEND_HID_EVENT request.");
    400             return;
    401         }
    402     }
    403 
    404     private void startServices() {
    405         mDisplaySinkService = new DisplaySinkService(this, mTransport,
    406                 getResources().getConfiguration().densityDpi);
    407         mDisplaySinkService.start();
    408 
    409         if (mAttached) {
    410             mDisplaySinkService.setSurfaceView(mSurfaceView);
    411         }
    412     }
    413 
    414     private void stopServices() {
    415         if (mDisplaySinkService != null) {
    416             mDisplaySinkService.stop();
    417             mDisplaySinkService = null;
    418         }
    419     }
    420 
    421     @Override
    422     public void onAttachedToWindow() {
    423         super.onAttachedToWindow();
    424 
    425         mAttached = true;
    426         if (mDisplaySinkService != null) {
    427             mDisplaySinkService.setSurfaceView(mSurfaceView);
    428         }
    429     }
    430 
    431     @Override
    432     public void onDetachedFromWindow() {
    433         super.onDetachedFromWindow();
    434 
    435         mAttached = false;
    436         if (mDisplaySinkService != null) {
    437             mDisplaySinkService.setSurfaceView(null);
    438         }
    439     }
    440 
    441     private int getProtocol(UsbDeviceConnection conn) {
    442         byte buffer[] = new byte[2];
    443         int len = conn.controlTransfer(
    444                 UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
    445                 UsbAccessoryConstants.ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, 10000);
    446         if (len != 2) {
    447             return -1;
    448         }
    449         return (buffer[1] << 8) | buffer[0];
    450     }
    451 
    452     private void sendString(UsbDeviceConnection conn, int index, String string) {
    453         byte[] buffer = (string + "\0").getBytes();
    454         int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
    455                 UsbAccessoryConstants.ACCESSORY_SEND_STRING, 0, index,
    456                 buffer, buffer.length, 10000);
    457         if (len != buffer.length) {
    458             mLogger.logError("Failed to send string " + index + ": \"" + string + "\"");
    459         } else {
    460             mLogger.log("Sent string " + index + ": \"" + string + "\"");
    461         }
    462     }
    463 
    464     private static boolean isAccessory(UsbDevice device) {
    465         final int vid = device.getVendorId();
    466         final int pid = device.getProductId();
    467         return vid == UsbAccessoryConstants.USB_ACCESSORY_VENDOR_ID
    468                 && (pid == UsbAccessoryConstants.USB_ACCESSORY_PRODUCT_ID
    469                         || pid == UsbAccessoryConstants.USB_ACCESSORY_ADB_PRODUCT_ID);
    470     }
    471 
    472     class TextLogger extends Logger {
    473         @Override
    474         public void log(final String message) {
    475             Log.d(TAG, message);
    476 
    477             mLogTextView.post(new Runnable() {
    478                 @Override
    479                 public void run() {
    480                     mLogTextView.append(message);
    481                     mLogTextView.append("\n");
    482                 }
    483             });
    484         }
    485     }
    486 
    487     class DeviceReceiver extends BroadcastReceiver {
    488         @Override
    489         public void onReceive(Context context, Intent intent) {
    490             UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
    491             if (device != null) {
    492                 String action = intent.getAction();
    493                 if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
    494                     onDeviceAttached(device);
    495                 } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
    496                     onDeviceDetached(device);
    497                 } else if (action.equals(ACTION_USB_DEVICE_PERMISSION)) {
    498                     if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
    499                         mLogger.log("Device permission granted: " + device);
    500                         onDeviceAttached(device);
    501                     } else {
    502                         mLogger.logError("Device permission denied: " + device);
    503                     }
    504                 }
    505             }
    506         }
    507     }
    508 }
    509