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 = (TextView) findViewById(R.id.logTextView); 99 mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance()); 100 mLogger = new TextLogger(); 101 102 mFpsTextView = (TextView) findViewById(R.id.fpsTextView); 103 104 mSurfaceView = (SurfaceView) 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