diff options
author | Jeff Brown <jeffbrown@google.com> | 2013-05-29 14:59:46 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2013-06-18 15:32:41 -0700 |
commit | 8f3b1307678fcd1896c7fb8ba4cc20553dc032e8 (patch) | |
tree | 9935dd8edf3a8380256502a132d040a4fd607df8 /tests/AccessoryDisplay/source | |
parent | a506a6ec94863a35acca9feb165db76ddac3892c (diff) | |
download | frameworks_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')
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 Binary files differnew file mode 100755 index 0000000..66a1984 --- /dev/null +++ b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png diff --git a/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png Binary files differnew file mode 100644 index 0000000..5ae7701 --- /dev/null +++ b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png 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 |