summaryrefslogtreecommitdiffstats
path: root/tests/AccessoryDisplay/source
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2013-05-29 14:59:46 -0700
committerJeff Brown <jeffbrown@google.com>2013-06-18 15:32:41 -0700
commit8f3b1307678fcd1896c7fb8ba4cc20553dc032e8 (patch)
tree9935dd8edf3a8380256502a132d040a4fd607df8 /tests/AccessoryDisplay/source
parenta506a6ec94863a35acca9feb165db76ddac3892c (diff)
downloadframeworks_base-8f3b1307678fcd1896c7fb8ba4cc20553dc032e8.zip
frameworks_base-8f3b1307678fcd1896c7fb8ba4cc20553dc032e8.tar.gz
frameworks_base-8f3b1307678fcd1896c7fb8ba4cc20553dc032e8.tar.bz2
Add test for streaming display contents to an accessory.
There are two applications: a source and a sink. They should be installed on two separate Android devices. Then connect the source device to the sink device using a USB OTG cable. Bug: 9192512 Change-Id: I99b552026684abbfd69cb13ab324e72fa16c36ab
Diffstat (limited to 'tests/AccessoryDisplay/source')
-rw-r--r--tests/AccessoryDisplay/source/Android.mk25
-rw-r--r--tests/AccessoryDisplay/source/AndroidManifest.xml41
-rwxr-xr-xtests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.pngbin0 -> 3608 bytes
-rw-r--r--tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.pngbin0 -> 5198 bytes
-rw-r--r--tests/AccessoryDisplay/source/res/layout/presentation_content.xml30
-rw-r--r--tests/AccessoryDisplay/source/res/layout/source_activity.xml24
-rw-r--r--tests/AccessoryDisplay/source/res/values/strings.xml19
-rw-r--r--tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml20
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java246
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java257
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java70
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java100
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java124
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java84
14 files changed, 1040 insertions, 0 deletions
diff --git a/tests/AccessoryDisplay/source/Android.mk b/tests/AccessoryDisplay/source/Android.mk
new file mode 100644
index 0000000..5d1085d
--- /dev/null
+++ b/tests/AccessoryDisplay/source/Android.mk
@@ -0,0 +1,25 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the application.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := AccessoryDisplaySource
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := AccessoryDisplayCommon
+include $(BUILD_PACKAGE)
diff --git a/tests/AccessoryDisplay/source/AndroidManifest.xml b/tests/AccessoryDisplay/source/AndroidManifest.xml
new file mode 100644
index 0000000..d3edcb8
--- /dev/null
+++ b/tests/AccessoryDisplay/source/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.accessorydisplay.source" >
+
+ <uses-feature android:name="android.hardware.usb.accessory"/>
+ <uses-sdk android:minSdkVersion="18" />
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_app"
+ android:hardwareAccelerated="true">
+
+ <activity android:name=".SourceActivity"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.Holo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
+ android:resource="@xml/usb_accessory_filter"/>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/source/res/layout/presentation_content.xml b/tests/AccessoryDisplay/source/res/layout/presentation_content.xml
new file mode 100644
index 0000000..bf9566a
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/layout/presentation_content.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.opengl.GLSurfaceView android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <Button android:id="@+id/explode_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:text="Explode!" />
+</RelativeLayout>
diff --git a/tests/AccessoryDisplay/source/res/layout/source_activity.xml b/tests/AccessoryDisplay/source/res/layout/source_activity.xml
new file mode 100644
index 0000000..ff2b818
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/layout/source_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:id="@+id/logTextView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</LinearLayout>
diff --git a/tests/AccessoryDisplay/source/res/values/strings.xml b/tests/AccessoryDisplay/source/res/values/strings.xml
new file mode 100644
index 0000000..0b488df
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Accessory Display Source</string>
+</resources>
diff --git a/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml b/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml
new file mode 100644
index 0000000..5313b4e
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <!-- Match all devices -->
+ <usb-accessory manufacturer="Android" model="Accessory Display" />
+</resources>
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java
new file mode 100644
index 0000000..ccead44
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java
@@ -0,0 +1,246 @@
+/*
+ * 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.source;
+
+import com.android.accessorydisplay.common.Protocol;
+import com.android.accessorydisplay.common.Service;
+import com.android.accessorydisplay.common.Transport;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Display;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+public class DisplaySourceService extends Service {
+ private static final int MSG_DISPATCH_DISPLAY_ADDED = 1;
+ private static final int MSG_DISPATCH_DISPLAY_REMOVED = 2;
+
+ private static final String DISPLAY_NAME = "Accessory Display";
+ private static final int BIT_RATE = 6000000;
+ private static final int FRAME_RATE = 30;
+ private static final int I_FRAME_INTERVAL = 10;
+
+ private final Callbacks mCallbacks;
+ private final ServiceHandler mHandler;
+ private final DisplayManager mDisplayManager;
+
+ private boolean mSinkAvailable;
+ private int mSinkWidth;
+ private int mSinkHeight;
+ private int mSinkDensityDpi;
+
+ private VirtualDisplayThread mVirtualDisplayThread;
+
+ public DisplaySourceService(Context context, Transport transport, Callbacks callbacks) {
+ super(context, transport, Protocol.DisplaySourceService.ID);
+ mCallbacks = callbacks;
+ mHandler = new ServiceHandler();
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ }
+
+ @Override
+ public void start() {
+ super.start();
+
+ getLogger().log("Sending MSG_QUERY.");
+ getTransport().sendMessage(Protocol.DisplaySinkService.ID,
+ Protocol.DisplaySinkService.MSG_QUERY, null);
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+
+ handleSinkNotAvailable();
+ }
+
+ @Override
+ public void onMessageReceived(int service, int what, ByteBuffer content) {
+ switch (what) {
+ case Protocol.DisplaySourceService.MSG_SINK_AVAILABLE: {
+ getLogger().log("Received MSG_SINK_AVAILABLE");
+ if (content.remaining() >= 12) {
+ final int width = content.getInt();
+ final int height = content.getInt();
+ final int densityDpi = content.getInt();
+ if (width >= 0 && width <= 4096
+ && height >= 0 && height <= 4096
+ && densityDpi >= 60 && densityDpi <= 640) {
+ handleSinkAvailable(width, height, densityDpi);
+ return;
+ }
+ }
+ getLogger().log("Receive invalid MSG_SINK_AVAILABLE message.");
+ break;
+ }
+
+ case Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE: {
+ getLogger().log("Received MSG_SINK_NOT_AVAILABLE");
+ handleSinkNotAvailable();
+ break;
+ }
+ }
+ }
+
+ private void handleSinkAvailable(int width, int height, int densityDpi) {
+ if (mSinkAvailable && mSinkWidth == width && mSinkHeight == height
+ && mSinkDensityDpi == densityDpi) {
+ return;
+ }
+
+ getLogger().log("Accessory display sink available: "
+ + "width=" + width + ", height=" + height
+ + ", densityDpi=" + densityDpi);
+ mSinkAvailable = true;
+ mSinkWidth = width;
+ mSinkHeight = height;
+ mSinkDensityDpi = densityDpi;
+ createVirtualDisplay();
+ }
+
+ private void handleSinkNotAvailable() {
+ getLogger().log("Accessory display sink not available.");
+
+ mSinkAvailable = false;
+ mSinkWidth = 0;
+ mSinkHeight = 0;
+ mSinkDensityDpi = 0;
+ releaseVirtualDisplay();
+ }
+
+ private void createVirtualDisplay() {
+ releaseVirtualDisplay();
+
+ mVirtualDisplayThread = new VirtualDisplayThread(
+ mSinkWidth, mSinkHeight, mSinkDensityDpi);
+ mVirtualDisplayThread.start();
+ }
+
+ private void releaseVirtualDisplay() {
+ if (mVirtualDisplayThread != null) {
+ mVirtualDisplayThread.quit();
+ mVirtualDisplayThread = null;
+ }
+ }
+
+ public interface Callbacks {
+ public void onDisplayAdded(Display display);
+ public void onDisplayRemoved(Display display);
+ }
+
+ private final class ServiceHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_DISPLAY_ADDED: {
+ mCallbacks.onDisplayAdded((Display)msg.obj);
+ break;
+ }
+
+ case MSG_DISPATCH_DISPLAY_REMOVED: {
+ mCallbacks.onDisplayRemoved((Display)msg.obj);
+ break;
+ }
+ }
+ }
+ }
+
+ private final class VirtualDisplayThread extends Thread {
+ private static final int TIMEOUT_USEC = 1000000;
+
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+
+ private volatile boolean mQuitting;
+
+ public VirtualDisplayThread(int width, int height, int densityDpi) {
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+ }
+
+ @Override
+ public void run() {
+ MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL);
+
+ MediaCodec codec = MediaCodec.createEncoderByType("video/avc");
+ codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ Surface surface = codec.createInputSurface();
+ codec.start();
+
+ VirtualDisplay virtualDisplay = mDisplayManager.createPrivateVirtualDisplay(
+ DISPLAY_NAME, mWidth, mHeight, mDensityDpi, surface);
+ if (virtualDisplay != null) {
+ mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_ADDED,
+ virtualDisplay.getDisplay()).sendToTarget();
+
+ stream(codec);
+
+ mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_REMOVED,
+ virtualDisplay.getDisplay()).sendToTarget();
+ virtualDisplay.release();
+ }
+
+ codec.signalEndOfInputStream();
+ codec.stop();
+ }
+
+ public void quit() {
+ mQuitting = true;
+ }
+
+ private void stream(MediaCodec codec) {
+ BufferInfo info = new BufferInfo();
+ ByteBuffer[] buffers = null;
+ while (!mQuitting) {
+ int index = codec.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (index >= 0) {
+ if (buffers == null) {
+ buffers = codec.getOutputBuffers();
+ }
+
+ ByteBuffer buffer = buffers[index];
+ buffer.limit(info.offset + info.size);
+ buffer.position(info.offset);
+
+ getTransport().sendMessage(Protocol.DisplaySinkService.ID,
+ Protocol.DisplaySinkService.MSG_CONTENT, buffer);
+ codec.releaseOutputBuffer(index, false);
+ } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ buffers = null;
+ } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ getLogger().log("Codec dequeue buffer timed out.");
+ }
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java
new file mode 100644
index 0000000..c59c958
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java
@@ -0,0 +1,257 @@
+/*
+ * 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.source;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.source.presentation.DemoPresentation;
+
+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.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.Display;
+import android.widget.TextView;
+
+public class SourceActivity extends Activity {
+ private static final String TAG = "SourceActivity";
+
+ private static final String ACTION_USB_ACCESSORY_PERMISSION =
+ "com.android.accessorydisplay.source.ACTION_USB_ACCESSORY_PERMISSION";
+
+ private static final String MANUFACTURER = "Android";
+ private static final String MODEL = "Accessory Display";
+
+ private UsbManager mUsbManager;
+ private AccessoryReceiver mReceiver;
+ private TextView mLogTextView;
+ private Logger mLogger;
+ private Presenter mPresenter;
+
+ private boolean mConnected;
+ private UsbAccessory mAccessory;
+ private UsbAccessoryStreamTransport mTransport;
+
+ private DisplaySourceService mDisplaySourceService;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+
+ setContentView(R.layout.source_activity);
+
+ mLogTextView = (TextView) findViewById(R.id.logTextView);
+ mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance());
+ mLogger = new TextLogger();
+ mPresenter = new Presenter();
+
+ mLogger.log("Waiting for accessory display sink to be attached to USB...");
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+ filter.addAction(ACTION_USB_ACCESSORY_PERMISSION);
+ mReceiver = new AccessoryReceiver();
+ registerReceiver(mReceiver, filter);
+
+ Intent intent = getIntent();
+ if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ UsbAccessory accessory =
+ (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ onAccessoryAttached(accessory);
+ }
+ } else {
+ UsbAccessory[] accessories = mUsbManager.getAccessoryList();
+ if (accessories != null) {
+ for (UsbAccessory accessory : accessories) {
+ onAccessoryAttached(accessory);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ //new DemoPresentation(this, getWindowManager().getDefaultDisplay()).show();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ private void onAccessoryAttached(UsbAccessory accessory) {
+ mLogger.log("USB accessory attached: " + accessory);
+ if (!mConnected) {
+ connect(accessory);
+ }
+ }
+
+ private void onAccessoryDetached(UsbAccessory accessory) {
+ mLogger.log("USB accessory detached: " + accessory);
+ if (mConnected && accessory.equals(mAccessory)) {
+ disconnect();
+ }
+ }
+
+ private void connect(UsbAccessory accessory) {
+ if (!isSink(accessory)) {
+ mLogger.log("Not connecting to USB accessory because it is not an accessory display sink: "
+ + accessory);
+ return;
+ }
+
+ if (mConnected) {
+ disconnect();
+ }
+
+ // Check whether we have permission to access the accessory.
+ if (!mUsbManager.hasPermission(accessory)) {
+ mLogger.log("Prompting the user for access to the accessory.");
+ Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION);
+ intent.setPackage(getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ mUsbManager.requestPermission(accessory, pendingIntent);
+ return;
+ }
+
+ // Open the accessory.
+ ParcelFileDescriptor fd = mUsbManager.openAccessory(accessory);
+ if (fd == null) {
+ mLogger.logError("Could not obtain accessory connection.");
+ return;
+ }
+
+ // All set.
+ mLogger.log("Connected.");
+ mConnected = true;
+ mAccessory = accessory;
+ mTransport = new UsbAccessoryStreamTransport(mLogger, fd);
+ startServices();
+ mTransport.startReading();
+ }
+
+ private void disconnect() {
+ mLogger.log("Disconnecting from accessory: " + mAccessory);
+ stopServices();
+
+ mLogger.log("Disconnected.");
+ mConnected = false;
+ mAccessory = null;
+ if (mTransport != null) {
+ mTransport.close();
+ mTransport = null;
+ }
+ }
+
+ private void startServices() {
+ mDisplaySourceService = new DisplaySourceService(this, mTransport, mPresenter);
+ mDisplaySourceService.start();
+ }
+
+ private void stopServices() {
+ if (mDisplaySourceService != null) {
+ mDisplaySourceService.stop();
+ mDisplaySourceService = null;
+ }
+ }
+
+ private static boolean isSink(UsbAccessory accessory) {
+ return MANUFACTURER.equals(accessory.getManufacturer())
+ && MODEL.equals(accessory.getModel());
+ }
+
+ 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 AccessoryReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ UsbAccessory accessory = intent.<UsbAccessory>getParcelableExtra(
+ UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ String action = intent.getAction();
+ if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ onAccessoryAttached(accessory);
+ } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) {
+ onAccessoryDetached(accessory);
+ } else if (action.equals(ACTION_USB_ACCESSORY_PERMISSION)) {
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+ mLogger.log("Accessory permission granted: " + accessory);
+ onAccessoryAttached(accessory);
+ } else {
+ mLogger.logError("Accessory permission denied: " + accessory);
+ }
+ }
+ }
+ }
+ }
+
+ class Presenter implements DisplaySourceService.Callbacks {
+ private DemoPresentation mPresentation;
+
+ @Override
+ public void onDisplayAdded(Display display) {
+ mLogger.log("Accessory display added: " + display);
+
+ mPresentation = new DemoPresentation(SourceActivity.this, display, mLogger);
+ mPresentation.show();
+ }
+
+ @Override
+ public void onDisplayRemoved(Display display) {
+ mLogger.log("Accessory display removed: " + display);
+
+ if (mPresentation != null) {
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java
new file mode 100644
index 0000000..c28f4359
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java
@@ -0,0 +1,70 @@
+/*
+ * 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.source;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.common.Transport;
+
+import android.hardware.usb.UsbAccessory;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Sends or receives messages over a file descriptor associated with a {@link UsbAccessory}.
+ */
+public class UsbAccessoryStreamTransport extends Transport {
+ private ParcelFileDescriptor mFd;
+ private FileInputStream mInputStream;
+ private FileOutputStream mOutputStream;
+
+ public UsbAccessoryStreamTransport(Logger logger, ParcelFileDescriptor fd) {
+ super(logger, 16384);
+ mFd = fd;
+ mInputStream = new FileInputStream(fd.getFileDescriptor());
+ mOutputStream = new FileOutputStream(fd.getFileDescriptor());
+ }
+
+ @Override
+ protected void ioClose() {
+ try {
+ mFd.close();
+ } catch (IOException ex) {
+ }
+ mFd = null;
+ mInputStream = null;
+ mOutputStream = null;
+ }
+
+ @Override
+ protected int ioRead(byte[] buffer, int offset, int count) throws IOException {
+ if (mInputStream == null) {
+ throw new IOException("Stream was closed.");
+ }
+ return mInputStream.read(buffer, offset, count);
+ }
+
+ @Override
+ protected void ioWrite(byte[] buffer, int offset, int count) throws IOException {
+ if (mOutputStream == null) {
+ throw new IOException("Stream was closed.");
+ }
+ mOutputStream.write(buffer, offset, count);
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java
new file mode 100644
index 0000000..51d8da9
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java
@@ -0,0 +1,100 @@
+/*
+ * 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.source.presentation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A vertex shaded cube.
+ */
+class Cube
+{
+ public Cube()
+ {
+ int one = 0x10000;
+ int vertices[] = {
+ -one, -one, -one,
+ one, -one, -one,
+ one, one, -one,
+ -one, one, -one,
+ -one, -one, one,
+ one, -one, one,
+ one, one, one,
+ -one, one, one,
+ };
+
+ int colors[] = {
+ 0, 0, 0, one,
+ one, 0, 0, one,
+ one, one, 0, one,
+ 0, one, 0, one,
+ 0, 0, one, one,
+ one, 0, one, one,
+ one, one, one, one,
+ 0, one, one, one,
+ };
+
+ byte indices[] = {
+ 0, 4, 5, 0, 5, 1,
+ 1, 5, 6, 1, 6, 2,
+ 2, 6, 7, 2, 7, 3,
+ 3, 7, 4, 3, 4, 0,
+ 4, 7, 6, 4, 6, 5,
+ 3, 0, 1, 3, 1, 2
+ };
+
+ // Buffers to be passed to gl*Pointer() functions
+ // must be direct, i.e., they must be placed on the
+ // native heap where the garbage collector cannot
+ // move them.
+ //
+ // Buffers with multi-byte datatypes (e.g., short, int, float)
+ // must have their byte order set to native order
+
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
+ vbb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = vbb.asIntBuffer();
+ mVertexBuffer.put(vertices);
+ mVertexBuffer.position(0);
+
+ ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
+ cbb.order(ByteOrder.nativeOrder());
+ mColorBuffer = cbb.asIntBuffer();
+ mColorBuffer.put(colors);
+ mColorBuffer.position(0);
+
+ mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
+ mIndexBuffer.put(indices);
+ mIndexBuffer.position(0);
+ }
+
+ public void draw(GL10 gl)
+ {
+ gl.glFrontFace(GL10.GL_CW);
+ gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
+ gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
+ gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
+ }
+
+ private IntBuffer mVertexBuffer;
+ private IntBuffer mColorBuffer;
+ private ByteBuffer mIndexBuffer;
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java
new file mode 100644
index 0000000..51dc82a
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java
@@ -0,0 +1,124 @@
+/*
+ * 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.source.presentation;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLSurfaceView;
+
+/**
+ * Render a pair of tumbling cubes.
+ */
+
+public class CubeRenderer implements GLSurfaceView.Renderer {
+ private boolean mTranslucentBackground;
+ private Cube mCube;
+ private float mAngle;
+ private float mScale = 1.0f;
+ private boolean mExploding;
+
+ public CubeRenderer(boolean useTranslucentBackground) {
+ mTranslucentBackground = useTranslucentBackground;
+ mCube = new Cube();
+ }
+
+ public void explode() {
+ mExploding = true;
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ /*
+ * Usually, the first thing one might want to do is to clear
+ * the screen. The most efficient way of doing this is to use
+ * glClear().
+ */
+
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ /*
+ * Now we're ready to draw some 3D objects
+ */
+
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+ gl.glTranslatef(0, 0, -3.0f);
+ gl.glRotatef(mAngle, 0, 1, 0);
+ gl.glRotatef(mAngle*0.25f, 1, 0, 0);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
+
+ gl.glScalef(mScale, mScale, mScale);
+ mCube.draw(gl);
+
+ gl.glRotatef(mAngle*2.0f, 0, 1, 1);
+ gl.glTranslatef(0.5f, 0.5f, 0.5f);
+
+ mCube.draw(gl);
+
+ mAngle += 1.2f;
+
+ if (mExploding) {
+ mScale *= 1.02f;
+ if (mScale > 4.0f) {
+ mScale = 1.0f;
+ mExploding = false;
+ }
+ }
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ gl.glViewport(0, 0, width, height);
+
+ /*
+ * Set our projection matrix. This doesn't have to be done
+ * each time we draw, but usually a new projection needs to
+ * be set when the viewport is resized.
+ */
+
+ float ratio = (float) width / height;
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ /*
+ * Some one-time OpenGL initialization can be made here
+ * probably based on features of this particular context
+ */
+ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+ GL10.GL_FASTEST);
+
+ if (mTranslucentBackground) {
+ gl.glClearColor(0,0,0,0);
+ } else {
+ gl.glClearColor(1,1,1,1);
+ }
+ gl.glEnable(GL10.GL_CULL_FACE);
+ gl.glShadeModel(GL10.GL_SMOOTH);
+ gl.glEnable(GL10.GL_DEPTH_TEST);
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java
new file mode 100644
index 0000000..517b7fc
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java
@@ -0,0 +1,84 @@
+/*
+ * 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.source.presentation;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.source.R;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * The presentation to show on the accessory display.
+ * <p>
+ * Note that this display may have different metrics from the display on which
+ * the main activity is showing so we must be careful to use the presentation's
+ * own {@link Context} whenever we load resources.
+ * </p>
+ */
+public final class DemoPresentation extends Presentation {
+ private final Logger mLogger;
+
+ private GLSurfaceView mSurfaceView;
+ private CubeRenderer mRenderer;
+ private Button mExplodeButton;
+
+ public DemoPresentation(Context context, Display display, Logger logger) {
+ super(context, display);
+ mLogger = logger;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Be sure to call the super class.
+ super.onCreate(savedInstanceState);
+
+ // Get the resources for the context of the presentation.
+ // Notice that we are getting the resources from the context of the presentation.
+ Resources r = getContext().getResources();
+
+ // Inflate the layout.
+ setContentView(R.layout.presentation_content);
+
+ // Set up the surface view for visual interest.
+ mRenderer = new CubeRenderer(false);
+ mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
+ mSurfaceView.setRenderer(mRenderer);
+
+ // Add a button.
+ mExplodeButton = (Button)findViewById(R.id.explode_button);
+ mExplodeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mRenderer.explode();
+ }
+ });
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mLogger.log("Received touch event: " + event);
+ return super.onTouchEvent(event);
+ }
+} \ No newline at end of file