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
This commit is contained in:
17
tests/AccessoryDisplay/Android.mk
Normal file
17
tests/AccessoryDisplay/Android.mk
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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)
|
||||
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
50
tests/AccessoryDisplay/README
Normal file
50
tests/AccessoryDisplay/README
Normal file
@@ -0,0 +1,50 @@
|
||||
This directory contains sample code to test the use of virtual
|
||||
displays created over an Android Open Accessories Protocol link.
|
||||
|
||||
--- DESCRIPTION ---
|
||||
|
||||
There are two applications with two distinct roles: a sink
|
||||
and a source.
|
||||
|
||||
1. Sink Application
|
||||
|
||||
The role of the sink is to emulate an external display that happens
|
||||
to be connected using the USB accessory protocol. Think of it as
|
||||
a monitor or video dock that the user will want to plug a phone into.
|
||||
|
||||
The sink application uses the UsbDevice APIs to receive connections
|
||||
from the source device over USB. The sink acts as a USB host
|
||||
in this arrangement and will provide power to the source.
|
||||
|
||||
The sink application decodes encoded video from the source and
|
||||
displays it in a SurfaceView. The sink also injects passes touch
|
||||
events to the source over USB HID.
|
||||
|
||||
2. Source Application
|
||||
|
||||
The role of the source is to present some content onto an external
|
||||
display that happens to be attached over USB. This is the typical
|
||||
role that a phone or tablet might have when the user is trying to
|
||||
play content to an external monitor.
|
||||
|
||||
The source application uses the UsbAccessory APIs to connect
|
||||
to the sink device over USB. The source acts as a USB peripheral
|
||||
in this arrangement and will receive power from the sink.
|
||||
|
||||
The source application uses the DisplayManager APIs to create
|
||||
a private virtual display which passes the framebuffer through
|
||||
an encoder and streams the output to the sink over USB. Then
|
||||
the application opens a Presentation on the new virtual display
|
||||
and shows a silly cube animation.
|
||||
|
||||
--- USAGE ---
|
||||
|
||||
These applications should be installed on two separate Android
|
||||
devices which are then connected using a USB OTG cable.
|
||||
Remember that the sink device is functioning as the USB host
|
||||
so the USB OTG cable should be plugged directly into it.
|
||||
|
||||
When connected, the applications should automatically launch
|
||||
on each device. The source will then begin to project display
|
||||
contents to the sink.
|
||||
|
||||
23
tests/AccessoryDisplay/common/Android.mk
Normal file
23
tests/AccessoryDisplay/common/Android.mk
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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_MODULE := AccessoryDisplayCommon
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SDK_VERSION := current
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.common;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Maintains a bounded pool of buffers. Attempts to acquire buffers beyond the maximum
|
||||
* count will block until other buffers are released.
|
||||
*/
|
||||
final class BufferPool {
|
||||
private final int mInitialBufferSize;
|
||||
private final int mMaxBufferSize;
|
||||
private final ByteBuffer[] mBuffers;
|
||||
private int mAllocated;
|
||||
private int mAvailable;
|
||||
|
||||
public BufferPool(int initialBufferSize, int maxBufferSize, int maxBuffers) {
|
||||
mInitialBufferSize = initialBufferSize;
|
||||
mMaxBufferSize = maxBufferSize;
|
||||
mBuffers = new ByteBuffer[maxBuffers];
|
||||
}
|
||||
|
||||
public ByteBuffer acquire(int needed) {
|
||||
synchronized (this) {
|
||||
for (;;) {
|
||||
if (mAvailable != 0) {
|
||||
mAvailable -= 1;
|
||||
return grow(mBuffers[mAvailable], needed);
|
||||
}
|
||||
|
||||
if (mAllocated < mBuffers.length) {
|
||||
mAllocated += 1;
|
||||
return ByteBuffer.allocate(chooseCapacity(mInitialBufferSize, needed));
|
||||
}
|
||||
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void release(ByteBuffer buffer) {
|
||||
synchronized (this) {
|
||||
buffer.clear();
|
||||
mBuffers[mAvailable++] = buffer;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer grow(ByteBuffer buffer, int needed) {
|
||||
int capacity = buffer.capacity();
|
||||
if (capacity < needed) {
|
||||
final ByteBuffer oldBuffer = buffer;
|
||||
capacity = chooseCapacity(capacity, needed);
|
||||
buffer = ByteBuffer.allocate(capacity);
|
||||
oldBuffer.flip();
|
||||
buffer.put(oldBuffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private int chooseCapacity(int capacity, int needed) {
|
||||
while (capacity < needed) {
|
||||
capacity *= 2;
|
||||
}
|
||||
if (capacity > mMaxBufferSize) {
|
||||
if (needed > mMaxBufferSize) {
|
||||
throw new IllegalArgumentException("Requested size " + needed
|
||||
+ " is larger than maximum buffer size " + mMaxBufferSize + ".");
|
||||
}
|
||||
capacity = mMaxBufferSize;
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.common;
|
||||
|
||||
public abstract class Logger {
|
||||
public abstract void log(String message);
|
||||
|
||||
public void logError(String message) {
|
||||
log("ERROR: " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.common;
|
||||
|
||||
/**
|
||||
* Defines message types.
|
||||
*/
|
||||
public class Protocol {
|
||||
// Message header.
|
||||
// 0: service id (16 bits)
|
||||
// 2: what (16 bits)
|
||||
// 4: content size (32 bits)
|
||||
// 8: ... content follows ...
|
||||
static final int HEADER_SIZE = 8;
|
||||
|
||||
// Maximum size of a message envelope including the header and contents.
|
||||
static final int MAX_ENVELOPE_SIZE = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Maximum message content size.
|
||||
*/
|
||||
public static final int MAX_CONTENT_SIZE = MAX_ENVELOPE_SIZE - HEADER_SIZE;
|
||||
|
||||
public static final class DisplaySinkService {
|
||||
private DisplaySinkService() { }
|
||||
|
||||
public static final int ID = 1;
|
||||
|
||||
// Query sink capabilities.
|
||||
// Replies with sink available or not available.
|
||||
public static final int MSG_QUERY = 1;
|
||||
|
||||
// Send MPEG2-TS H.264 encoded content.
|
||||
public static final int MSG_CONTENT = 2;
|
||||
}
|
||||
|
||||
public static final class DisplaySourceService {
|
||||
private DisplaySourceService() { }
|
||||
|
||||
public static final int ID = 2;
|
||||
|
||||
// Sink is now available for use.
|
||||
// 0: width (32 bits)
|
||||
// 4: height (32 bits)
|
||||
// 8: density dpi (32 bits)
|
||||
public static final int MSG_SINK_AVAILABLE = 1;
|
||||
|
||||
// Sink is no longer available for use.
|
||||
public static final int MSG_SINK_NOT_AVAILABLE = 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.common;
|
||||
|
||||
import com.android.accessorydisplay.common.Transport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Base implementation of a service that communicates over a transport.
|
||||
* <p>
|
||||
* This object's interface is single-threaded. It is only intended to be
|
||||
* accessed from the {@link Looper} thread on which the transport was created.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class Service implements Transport.Callback {
|
||||
private final Context mContext;
|
||||
private final Transport mTransport;
|
||||
private final int mServiceId;
|
||||
|
||||
public Service(Context context, Transport transport, int serviceId) {
|
||||
mContext = context;
|
||||
mTransport = transport;
|
||||
mServiceId = serviceId;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getServiceId() {
|
||||
return mServiceId;
|
||||
}
|
||||
|
||||
public Transport getTransport() {
|
||||
return mTransport;
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return mTransport.getLogger();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mTransport.registerService(mServiceId, this);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
mTransport.unregisterService(mServiceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(int service, int what, ByteBuffer content) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* 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.common;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A simple message transport.
|
||||
* <p>
|
||||
* This object's interface is thread-safe, however incoming messages
|
||||
* are always delivered on the {@link Looper} thread on which the transport
|
||||
* was created.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class Transport {
|
||||
private static final int MAX_INPUT_BUFFERS = 8;
|
||||
|
||||
private final Logger mLogger;
|
||||
|
||||
// The transport thread looper and handler.
|
||||
private final TransportHandler mHandler;
|
||||
|
||||
// Lock to guard all mutable state.
|
||||
private final Object mLock = new Object();
|
||||
|
||||
// The output buffer. Set to null when the transport is closed.
|
||||
private ByteBuffer mOutputBuffer;
|
||||
|
||||
// The input buffer pool.
|
||||
private BufferPool mInputBufferPool;
|
||||
|
||||
// The reader thread. Initialized when reading starts.
|
||||
private ReaderThread mThread;
|
||||
|
||||
// The list of callbacks indexed by service id.
|
||||
private final SparseArray<Callback> mServices = new SparseArray<Callback>();
|
||||
|
||||
public Transport(Logger logger, int maxPacketSize) {
|
||||
mLogger = logger;
|
||||
mHandler = new TransportHandler();
|
||||
mOutputBuffer = ByteBuffer.allocate(maxPacketSize);
|
||||
mInputBufferPool = new BufferPool(
|
||||
maxPacketSize, Protocol.MAX_ENVELOPE_SIZE, MAX_INPUT_BUFFERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the logger for debugging.
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return mLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler on the transport's thread.
|
||||
*/
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*/
|
||||
public void close() {
|
||||
synchronized (mLock) {
|
||||
if (mOutputBuffer != null) {
|
||||
if (mThread == null) {
|
||||
ioClose();
|
||||
} else {
|
||||
// If the thread was started then it will be responsible for
|
||||
// closing the stream when it quits because it may currently
|
||||
// be in the process of reading from the stream so we can't simply
|
||||
// shut it down right now.
|
||||
mThread.quit();
|
||||
}
|
||||
mOutputBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param service The service to whom the message is addressed.
|
||||
* @param what The message type.
|
||||
* @param content The content, or null if there is none.
|
||||
* @return True if the message was sent successfully, false if an error occurred.
|
||||
*/
|
||||
public boolean sendMessage(int service, int what, ByteBuffer content) {
|
||||
checkServiceId(service);
|
||||
checkMessageId(what);
|
||||
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
if (mOutputBuffer == null) {
|
||||
mLogger.logError("Send message failed because transport was closed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] outputArray = mOutputBuffer.array();
|
||||
final int capacity = mOutputBuffer.capacity();
|
||||
mOutputBuffer.clear();
|
||||
mOutputBuffer.putShort((short)service);
|
||||
mOutputBuffer.putShort((short)what);
|
||||
if (content == null) {
|
||||
mOutputBuffer.putInt(0);
|
||||
} else {
|
||||
final int contentLimit = content.limit();
|
||||
int contentPosition = content.position();
|
||||
int contentRemaining = contentLimit - contentPosition;
|
||||
if (contentRemaining > Protocol.MAX_CONTENT_SIZE) {
|
||||
throw new IllegalArgumentException("Message content too large: "
|
||||
+ contentRemaining + " > " + Protocol.MAX_CONTENT_SIZE);
|
||||
}
|
||||
mOutputBuffer.putInt(contentRemaining);
|
||||
while (contentRemaining != 0) {
|
||||
final int outputAvailable = capacity - mOutputBuffer.position();
|
||||
if (contentRemaining <= outputAvailable) {
|
||||
mOutputBuffer.put(content);
|
||||
break;
|
||||
}
|
||||
content.limit(contentPosition + outputAvailable);
|
||||
mOutputBuffer.put(content);
|
||||
content.limit(contentLimit);
|
||||
ioWrite(outputArray, 0, capacity);
|
||||
contentPosition += outputAvailable;
|
||||
contentRemaining -= outputAvailable;
|
||||
mOutputBuffer.clear();
|
||||
}
|
||||
}
|
||||
ioWrite(outputArray, 0, mOutputBuffer.position());
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
mLogger.logError("Send message failed: " + ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts reading messages on a separate thread.
|
||||
*/
|
||||
public void startReading() {
|
||||
synchronized (mLock) {
|
||||
if (mOutputBuffer == null) {
|
||||
throw new IllegalStateException("Transport has been closed");
|
||||
}
|
||||
|
||||
mThread = new ReaderThread();
|
||||
mThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a service and provides a callback to receive messages.
|
||||
*
|
||||
* @param service The service id.
|
||||
* @param callback The callback to use.
|
||||
*/
|
||||
public void registerService(int service, Callback callback) {
|
||||
checkServiceId(service);
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
mServices.put(service, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a service.
|
||||
*
|
||||
* @param service The service to unregister.
|
||||
*/
|
||||
public void unregisterService(int service) {
|
||||
checkServiceId(service);
|
||||
|
||||
synchronized (mLock) {
|
||||
mServices.remove(service);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchMessageReceived(int service, int what, ByteBuffer content) {
|
||||
final Callback callback;
|
||||
synchronized (mLock) {
|
||||
callback = mServices.get(service);
|
||||
}
|
||||
if (callback != null) {
|
||||
callback.onMessageReceived(service, what, content);
|
||||
} else {
|
||||
mLogger.log("Discarding message " + what
|
||||
+ " for unregistered service " + service);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkServiceId(int service) {
|
||||
if (service < 0 || service > 0xffff) {
|
||||
throw new IllegalArgumentException("service id out of range: " + service);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkMessageId(int what) {
|
||||
if (what < 0 || what > 0xffff) {
|
||||
throw new IllegalArgumentException("message id out of range: " + what);
|
||||
}
|
||||
}
|
||||
|
||||
// The IO methods must be safe to call on any thread.
|
||||
// They may be called concurrently.
|
||||
protected abstract void ioClose();
|
||||
protected abstract int ioRead(byte[] buffer, int offset, int count)
|
||||
throws IOException;
|
||||
protected abstract void ioWrite(byte[] buffer, int offset, int count)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Callback for services that handle received messages.
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Indicates that a message was received.
|
||||
*
|
||||
* @param service The service to whom the message is addressed.
|
||||
* @param what The message type.
|
||||
* @param content The content, or null if there is none.
|
||||
*/
|
||||
public void onMessageReceived(int service, int what, ByteBuffer content);
|
||||
}
|
||||
|
||||
final class TransportHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final ByteBuffer buffer = (ByteBuffer)msg.obj;
|
||||
try {
|
||||
final int limit = buffer.limit();
|
||||
while (buffer.position() < limit) {
|
||||
final int service = buffer.getShort() & 0xffff;
|
||||
final int what = buffer.getShort() & 0xffff;
|
||||
final int contentSize = buffer.getInt();
|
||||
if (contentSize == 0) {
|
||||
dispatchMessageReceived(service, what, null);
|
||||
} else {
|
||||
final int end = buffer.position() + contentSize;
|
||||
buffer.limit(end);
|
||||
dispatchMessageReceived(service, what, buffer);
|
||||
buffer.limit(limit);
|
||||
buffer.position(end);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mInputBufferPool.release(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ReaderThread extends Thread {
|
||||
// Set to true when quitting.
|
||||
private volatile boolean mQuitting;
|
||||
|
||||
public ReaderThread() {
|
||||
super("Accessory Display Transport");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
loop();
|
||||
ioClose();
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
ByteBuffer buffer = null;
|
||||
int length = Protocol.HEADER_SIZE;
|
||||
int contentSize = -1;
|
||||
outer: while (!mQuitting) {
|
||||
// Get a buffer.
|
||||
if (buffer == null) {
|
||||
buffer = mInputBufferPool.acquire(length);
|
||||
} else {
|
||||
buffer = mInputBufferPool.grow(buffer, length);
|
||||
}
|
||||
|
||||
// Read more data until needed number of bytes obtained.
|
||||
int position = buffer.position();
|
||||
int count;
|
||||
try {
|
||||
count = ioRead(buffer.array(), position, buffer.capacity() - position);
|
||||
if (count < 0) {
|
||||
break; // end of stream
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
mLogger.logError("Read failed: " + ex);
|
||||
break; // error
|
||||
}
|
||||
position += count;
|
||||
buffer.position(position);
|
||||
if (contentSize < 0 && position >= Protocol.HEADER_SIZE) {
|
||||
contentSize = buffer.getInt(4);
|
||||
if (contentSize < 0 || contentSize > Protocol.MAX_CONTENT_SIZE) {
|
||||
mLogger.logError("Encountered invalid content size: " + contentSize);
|
||||
break; // malformed stream
|
||||
}
|
||||
length += contentSize;
|
||||
}
|
||||
if (position < length) {
|
||||
continue; // need more data
|
||||
}
|
||||
|
||||
// There is at least one complete message in the buffer.
|
||||
// Find the end of a contiguous chunk of complete messages.
|
||||
int next = length;
|
||||
int remaining;
|
||||
for (;;) {
|
||||
length = Protocol.HEADER_SIZE;
|
||||
remaining = position - next;
|
||||
if (remaining < length) {
|
||||
contentSize = -1;
|
||||
break; // incomplete header, need more data
|
||||
}
|
||||
contentSize = buffer.getInt(next + 4);
|
||||
if (contentSize < 0 || contentSize > Protocol.MAX_CONTENT_SIZE) {
|
||||
mLogger.logError("Encountered invalid content size: " + contentSize);
|
||||
break outer; // malformed stream
|
||||
}
|
||||
length += contentSize;
|
||||
if (remaining < length) {
|
||||
break; // incomplete content, need more data
|
||||
}
|
||||
next += length;
|
||||
}
|
||||
|
||||
// Post the buffer then don't modify it anymore.
|
||||
// Now this is kind of sneaky. We know that no other threads will
|
||||
// be acquiring buffers from the buffer pool so we can keep on
|
||||
// referring to this buffer as long as we don't modify its contents.
|
||||
// This allows us to operate in a single-buffered mode if desired.
|
||||
buffer.limit(next);
|
||||
buffer.rewind();
|
||||
mHandler.obtainMessage(0, buffer).sendToTarget();
|
||||
|
||||
// If there is an incomplete message at the end, then we will need
|
||||
// to copy it to a fresh buffer before continuing. In the single-buffered
|
||||
// case, we may acquire the same buffer as before which is fine.
|
||||
if (remaining == 0) {
|
||||
buffer = null;
|
||||
} else {
|
||||
final ByteBuffer oldBuffer = buffer;
|
||||
buffer = mInputBufferPool.acquire(length);
|
||||
System.arraycopy(oldBuffer.array(), next, buffer.array(), 0, remaining);
|
||||
buffer.position(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer != null) {
|
||||
mInputBufferPool.release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void quit() {
|
||||
mQuitting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
tests/AccessoryDisplay/sink/Android.mk
Normal file
25
tests/AccessoryDisplay/sink/Android.mk
Normal file
@@ -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 := AccessoryDisplaySink
|
||||
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)
|
||||
41
tests/AccessoryDisplay/sink/AndroidManifest.xml
Normal file
41
tests/AccessoryDisplay/sink/AndroidManifest.xml
Normal file
@@ -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.sink" >
|
||||
|
||||
<uses-feature android:name="android.hardware.usb.host"/>
|
||||
<uses-sdk android:minSdkVersion="18" />
|
||||
|
||||
<application android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_app"
|
||||
android:hardwareAccelerated="true">
|
||||
|
||||
<activity android:name=".SinkActivity"
|
||||
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_DEVICE_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_DEVICE_ATTACHED"
|
||||
android:resource="@xml/usb_device_filter"/>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png
Executable file
BIN
tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.png
Normal file
BIN
tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
44
tests/AccessoryDisplay/sink/res/layout/sink_activity.xml
Normal file
44
tests/AccessoryDisplay/sink/res/layout/sink_activity.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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="horizontal">
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/fpsTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:padding="4dp" />
|
||||
|
||||
<TextView android:id="@+id/logTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="2">
|
||||
<SurfaceView android:id="@+id/surfaceView"
|
||||
android:layout_width="640px"
|
||||
android:layout_height="480px" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
19
tests/AccessoryDisplay/sink/res/values/strings.xml
Normal file
19
tests/AccessoryDisplay/sink/res/values/strings.xml
Normal file
@@ -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 Sink</string>
|
||||
</resources>
|
||||
25
tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml
Normal file
25
tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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-device />
|
||||
|
||||
<!-- Android USB accessory: accessory -->
|
||||
<usb-device vendor-id="16601" product-id="11520" />
|
||||
<!-- Android USB accessory: accessory + adb -->
|
||||
<usb-device vendor-id="16601" product-id="11521" />
|
||||
</resources>
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.sink;
|
||||
|
||||
import com.android.accessorydisplay.common.Protocol;
|
||||
import com.android.accessorydisplay.common.Service;
|
||||
import com.android.accessorydisplay.common.Transport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DisplaySinkService extends Service implements SurfaceHolder.Callback {
|
||||
private final ByteBuffer mBuffer = ByteBuffer.allocate(12);
|
||||
private final Handler mTransportHandler;
|
||||
private final int mDensityDpi;
|
||||
|
||||
private SurfaceView mSurfaceView;
|
||||
|
||||
// These fields are guarded by the following lock.
|
||||
// This is to ensure that the surface lifecycle is respected. Although decoding
|
||||
// happens on the transport thread, we are not allowed to access the surface after
|
||||
// it is destroyed by the UI thread so we need to stop the codec immediately.
|
||||
private final Object mSurfaceAndCodecLock = new Object();
|
||||
private Surface mSurface;
|
||||
private int mSurfaceWidth;
|
||||
private int mSurfaceHeight;
|
||||
private MediaCodec mCodec;
|
||||
private ByteBuffer[] mCodecInputBuffers;
|
||||
private BufferInfo mCodecBufferInfo;
|
||||
|
||||
public DisplaySinkService(Context context, Transport transport, int densityDpi) {
|
||||
super(context, transport, Protocol.DisplaySinkService.ID);
|
||||
mTransportHandler = transport.getHandler();
|
||||
mDensityDpi = densityDpi;
|
||||
}
|
||||
|
||||
public void setSurfaceView(final SurfaceView surfaceView) {
|
||||
if (mSurfaceView != surfaceView) {
|
||||
final SurfaceView oldSurfaceView = mSurfaceView;
|
||||
mSurfaceView = surfaceView;
|
||||
|
||||
if (oldSurfaceView != null) {
|
||||
oldSurfaceView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final SurfaceHolder holder = oldSurfaceView.getHolder();
|
||||
holder.removeCallback(DisplaySinkService.this);
|
||||
updateSurfaceFromUi(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (surfaceView != null) {
|
||||
surfaceView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final SurfaceHolder holder = surfaceView.getHolder();
|
||||
holder.addCallback(DisplaySinkService.this);
|
||||
updateSurfaceFromUi(holder);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(int service, int what, ByteBuffer content) {
|
||||
switch (what) {
|
||||
case Protocol.DisplaySinkService.MSG_QUERY: {
|
||||
getLogger().log("Received MSG_QUERY.");
|
||||
sendSinkStatus();
|
||||
break;
|
||||
}
|
||||
|
||||
case Protocol.DisplaySinkService.MSG_CONTENT: {
|
||||
decode(content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
// Ignore. Wait for surface changed event that follows.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
updateSurfaceFromUi(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
updateSurfaceFromUi(null);
|
||||
}
|
||||
|
||||
private void updateSurfaceFromUi(SurfaceHolder holder) {
|
||||
Surface surface = null;
|
||||
int width = 0, height = 0;
|
||||
if (holder != null && !holder.isCreating()) {
|
||||
surface = holder.getSurface();
|
||||
if (surface.isValid()) {
|
||||
final Rect frame = holder.getSurfaceFrame();
|
||||
width = frame.width();
|
||||
height = frame.height();
|
||||
} else {
|
||||
surface = null;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mSurfaceAndCodecLock) {
|
||||
if (mSurface == surface && mSurfaceWidth == width && mSurfaceHeight == height) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSurface = surface;
|
||||
mSurfaceWidth = width;
|
||||
mSurfaceHeight = height;
|
||||
|
||||
if (mCodec != null) {
|
||||
mCodec.stop();
|
||||
mCodec = null;
|
||||
mCodecInputBuffers = null;
|
||||
mCodecBufferInfo = null;
|
||||
}
|
||||
|
||||
if (mSurface != null) {
|
||||
MediaFormat format = MediaFormat.createVideoFormat(
|
||||
"video/avc", mSurfaceWidth, mSurfaceHeight);
|
||||
mCodec = MediaCodec.createDecoderByType("video/avc");
|
||||
mCodec.configure(format, mSurface, null, 0);
|
||||
mCodec.start();
|
||||
mCodecBufferInfo = new BufferInfo();
|
||||
}
|
||||
|
||||
mTransportHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendSinkStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void decode(ByteBuffer content) {
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (mSurfaceAndCodecLock) {
|
||||
if (mCodec == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (content.hasRemaining()) {
|
||||
if (!provideCodecInputLocked(content)) {
|
||||
getLogger().log("Dropping content because there are no available buffers.");
|
||||
return;
|
||||
}
|
||||
|
||||
consumeCodecOutputLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean provideCodecInputLocked(ByteBuffer content) {
|
||||
final int index = mCodec.dequeueInputBuffer(0);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
if (mCodecInputBuffers == null) {
|
||||
mCodecInputBuffers = mCodec.getInputBuffers();
|
||||
}
|
||||
final ByteBuffer buffer = mCodecInputBuffers[index];
|
||||
final int capacity = buffer.capacity();
|
||||
buffer.clear();
|
||||
if (content.remaining() <= capacity) {
|
||||
buffer.put(content);
|
||||
} else {
|
||||
final int limit = content.limit();
|
||||
content.limit(content.position() + capacity);
|
||||
buffer.put(content);
|
||||
content.limit(limit);
|
||||
}
|
||||
buffer.flip();
|
||||
mCodec.queueInputBuffer(index, 0, buffer.limit(), 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void consumeCodecOutputLocked() {
|
||||
for (;;) {
|
||||
final int index = mCodec.dequeueOutputBuffer(mCodecBufferInfo, 0);
|
||||
if (index >= 0) {
|
||||
mCodec.releaseOutputBuffer(index, true /*render*/);
|
||||
} else if (index != MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
|
||||
&& index != MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSinkStatus() {
|
||||
synchronized (mSurfaceAndCodecLock) {
|
||||
if (mCodec != null) {
|
||||
mBuffer.clear();
|
||||
mBuffer.putInt(mSurfaceWidth);
|
||||
mBuffer.putInt(mSurfaceHeight);
|
||||
mBuffer.putInt(mDensityDpi);
|
||||
mBuffer.flip();
|
||||
getTransport().sendMessage(Protocol.DisplaySourceService.ID,
|
||||
Protocol.DisplaySourceService.MSG_SINK_AVAILABLE, mBuffer);
|
||||
} else {
|
||||
getTransport().sendMessage(Protocol.DisplaySourceService.ID,
|
||||
Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.sink;
|
||||
|
||||
import com.android.accessorydisplay.common.Logger;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
public class SinkActivity extends Activity {
|
||||
private static final String TAG = "SinkActivity";
|
||||
|
||||
private static final String ACTION_USB_DEVICE_PERMISSION =
|
||||
"com.android.accessorydisplay.sink.ACTION_USB_DEVICE_PERMISSION";
|
||||
|
||||
private static final String MANUFACTURER = "Android";
|
||||
private static final String MODEL = "Accessory Display";
|
||||
private static final String DESCRIPTION = "Accessory Display Sink Test Application";
|
||||
private static final String VERSION = "1.0";
|
||||
private static final String URI = "http://www.android.com/";
|
||||
private static final String SERIAL = "0000000012345678";
|
||||
|
||||
private static final int MULTITOUCH_DEVICE_ID = 0;
|
||||
private static final int MULTITOUCH_REPORT_ID = 1;
|
||||
private static final int MULTITOUCH_MAX_CONTACTS = 1;
|
||||
|
||||
private UsbManager mUsbManager;
|
||||
private DeviceReceiver mReceiver;
|
||||
private TextView mLogTextView;
|
||||
private TextView mFpsTextView;
|
||||
private SurfaceView mSurfaceView;
|
||||
private Logger mLogger;
|
||||
|
||||
private boolean mConnected;
|
||||
private int mProtocolVersion;
|
||||
private UsbDevice mDevice;
|
||||
private UsbInterface mAccessoryInterface;
|
||||
private UsbDeviceConnection mAccessoryConnection;
|
||||
private UsbEndpoint mControlEndpoint;
|
||||
private UsbAccessoryBulkTransport mTransport;
|
||||
|
||||
private boolean mAttached;
|
||||
private DisplaySinkService mDisplaySinkService;
|
||||
|
||||
private final ByteBuffer mHidBuffer = ByteBuffer.allocate(4096);
|
||||
private UsbHid.Multitouch mMultitouch;
|
||||
private boolean mMultitouchEnabled;
|
||||
private UsbHid.Multitouch.Contact[] mMultitouchContacts;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
|
||||
|
||||
setContentView(R.layout.sink_activity);
|
||||
|
||||
mLogTextView = (TextView) findViewById(R.id.logTextView);
|
||||
mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||
mLogger = new TextLogger();
|
||||
|
||||
mFpsTextView = (TextView) findViewById(R.id.fpsTextView);
|
||||
|
||||
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
|
||||
mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
sendHidTouch(event);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mLogger.log("Waiting for accessory display source to be attached to USB...");
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(ACTION_USB_DEVICE_PERMISSION);
|
||||
mReceiver = new DeviceReceiver();
|
||||
registerReceiver(mReceiver, filter);
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
if (device != null) {
|
||||
onDeviceAttached(device);
|
||||
}
|
||||
} else {
|
||||
Map<String, UsbDevice> devices = mUsbManager.getDeviceList();
|
||||
if (devices != null) {
|
||||
for (UsbDevice device : devices.values()) {
|
||||
onDeviceAttached(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
private void onDeviceAttached(UsbDevice device) {
|
||||
mLogger.log("USB device attached: " + device);
|
||||
if (!mConnected) {
|
||||
connect(device);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDeviceDetached(UsbDevice device) {
|
||||
mLogger.log("USB device detached: " + device);
|
||||
if (mConnected && device.equals(mDevice)) {
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(UsbDevice device) {
|
||||
if (mConnected) {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// Check whether we have permission to access the device.
|
||||
if (!mUsbManager.hasPermission(device)) {
|
||||
mLogger.log("Prompting the user for access to the device.");
|
||||
Intent intent = new Intent(ACTION_USB_DEVICE_PERMISSION);
|
||||
intent.setPackage(getPackageName());
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
mUsbManager.requestPermission(device, pendingIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
// Claim the device.
|
||||
UsbDeviceConnection conn = mUsbManager.openDevice(device);
|
||||
if (conn == null) {
|
||||
mLogger.logError("Could not obtain device connection.");
|
||||
return;
|
||||
}
|
||||
UsbInterface iface = device.getInterface(0);
|
||||
UsbEndpoint controlEndpoint = iface.getEndpoint(0);
|
||||
if (!conn.claimInterface(iface, true)) {
|
||||
mLogger.logError("Could not claim interface.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// If already in accessory mode, then connect to the device.
|
||||
if (isAccessory(device)) {
|
||||
mLogger.log("Connecting to accessory...");
|
||||
|
||||
int protocolVersion = getProtocol(conn);
|
||||
if (protocolVersion < 1) {
|
||||
mLogger.logError("Device does not support accessory protocol.");
|
||||
return;
|
||||
}
|
||||
mLogger.log("Protocol version: " + protocolVersion);
|
||||
|
||||
// Setup bulk endpoints.
|
||||
UsbEndpoint bulkIn = null;
|
||||
UsbEndpoint bulkOut = null;
|
||||
for (int i = 0; i < iface.getEndpointCount(); i++) {
|
||||
UsbEndpoint ep = iface.getEndpoint(i);
|
||||
if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
|
||||
if (bulkIn == null) {
|
||||
mLogger.log(String.format("Bulk IN endpoint: %d", i));
|
||||
bulkIn = ep;
|
||||
}
|
||||
} else {
|
||||
if (bulkOut == null) {
|
||||
mLogger.log(String.format("Bulk OUT endpoint: %d", i));
|
||||
bulkOut = ep;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bulkIn == null || bulkOut == null) {
|
||||
mLogger.logError("Unable to find bulk endpoints");
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger.log("Connected");
|
||||
mConnected = true;
|
||||
mDevice = device;
|
||||
mProtocolVersion = protocolVersion;
|
||||
mAccessoryInterface = iface;
|
||||
mAccessoryConnection = conn;
|
||||
mControlEndpoint = controlEndpoint;
|
||||
mTransport = new UsbAccessoryBulkTransport(mLogger, conn, bulkIn, bulkOut);
|
||||
if (mProtocolVersion >= 2) {
|
||||
registerHid();
|
||||
}
|
||||
startServices();
|
||||
mTransport.startReading();
|
||||
return;
|
||||
}
|
||||
|
||||
// Do accessory negotiation.
|
||||
mLogger.log("Attempting to switch device to accessory mode...");
|
||||
|
||||
// Send get protocol.
|
||||
int protocolVersion = getProtocol(conn);
|
||||
if (protocolVersion < 1) {
|
||||
mLogger.logError("Device does not support accessory protocol.");
|
||||
return;
|
||||
}
|
||||
mLogger.log("Protocol version: " + protocolVersion);
|
||||
|
||||
// Send identifying strings.
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MANUFACTURER, MANUFACTURER);
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_MODEL, MODEL);
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_DESCRIPTION, DESCRIPTION);
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_VERSION, VERSION);
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_URI, URI);
|
||||
sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_SERIAL, SERIAL);
|
||||
|
||||
// Send start.
|
||||
// The device should re-enumerate as an accessory.
|
||||
mLogger.log("Sending accessory start request.");
|
||||
int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_START, 0, 0, null, 0, 10000);
|
||||
if (len != 0) {
|
||||
mLogger.logError("Device refused to switch to accessory mode.");
|
||||
} else {
|
||||
mLogger.log("Waiting for device to re-enumerate...");
|
||||
}
|
||||
} finally {
|
||||
if (!mConnected) {
|
||||
conn.releaseInterface(iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
mLogger.log("Disconnecting from device: " + mDevice);
|
||||
stopServices();
|
||||
unregisterHid();
|
||||
|
||||
mLogger.log("Disconnected.");
|
||||
mConnected = false;
|
||||
mDevice = null;
|
||||
mAccessoryConnection = null;
|
||||
mAccessoryInterface = null;
|
||||
mControlEndpoint = null;
|
||||
if (mTransport != null) {
|
||||
mTransport.close();
|
||||
mTransport = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHid() {
|
||||
mLogger.log("Registering HID multitouch device.");
|
||||
|
||||
mMultitouch = new UsbHid.Multitouch(MULTITOUCH_REPORT_ID, MULTITOUCH_MAX_CONTACTS,
|
||||
mSurfaceView.getWidth(), mSurfaceView.getHeight());
|
||||
|
||||
mHidBuffer.clear();
|
||||
mMultitouch.generateDescriptor(mHidBuffer);
|
||||
mHidBuffer.flip();
|
||||
|
||||
mLogger.log("HID descriptor size: " + mHidBuffer.limit());
|
||||
mLogger.log("HID report size: " + mMultitouch.getReportSize());
|
||||
|
||||
final int maxPacketSize = mControlEndpoint.getMaxPacketSize();
|
||||
mLogger.log("Control endpoint max packet size: " + maxPacketSize);
|
||||
if (mMultitouch.getReportSize() > maxPacketSize) {
|
||||
mLogger.logError("HID report is too big for this accessory.");
|
||||
return;
|
||||
}
|
||||
|
||||
int len = mAccessoryConnection.controlTransfer(
|
||||
UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_REGISTER_HID,
|
||||
MULTITOUCH_DEVICE_ID, mHidBuffer.limit(), null, 0, 10000);
|
||||
if (len != 0) {
|
||||
mLogger.logError("Device rejected ACCESSORY_REGISTER_HID request.");
|
||||
return;
|
||||
}
|
||||
|
||||
while (mHidBuffer.hasRemaining()) {
|
||||
int position = mHidBuffer.position();
|
||||
int count = Math.min(mHidBuffer.remaining(), maxPacketSize);
|
||||
len = mAccessoryConnection.controlTransfer(
|
||||
UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_SET_HID_REPORT_DESC,
|
||||
MULTITOUCH_DEVICE_ID, 0,
|
||||
mHidBuffer.array(), position, count, 10000);
|
||||
if (len != count) {
|
||||
mLogger.logError("Device rejected ACCESSORY_SET_HID_REPORT_DESC request.");
|
||||
return;
|
||||
}
|
||||
mHidBuffer.position(position + count);
|
||||
}
|
||||
|
||||
mLogger.log("HID device registered.");
|
||||
|
||||
mMultitouchEnabled = true;
|
||||
if (mMultitouchContacts == null) {
|
||||
mMultitouchContacts = new UsbHid.Multitouch.Contact[MULTITOUCH_MAX_CONTACTS];
|
||||
for (int i = 0; i < MULTITOUCH_MAX_CONTACTS; i++) {
|
||||
mMultitouchContacts[i] = new UsbHid.Multitouch.Contact();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterHid() {
|
||||
mMultitouch = null;
|
||||
mMultitouchContacts = null;
|
||||
mMultitouchEnabled = false;
|
||||
}
|
||||
|
||||
private void sendHidTouch(MotionEvent event) {
|
||||
if (mMultitouchEnabled) {
|
||||
mLogger.log("Sending touch event: " + event);
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerCount =
|
||||
Math.min(MULTITOUCH_MAX_CONTACTS, event.getPointerCount());
|
||||
final int historySize = event.getHistorySize();
|
||||
for (int p = 0; p < pointerCount; p++) {
|
||||
mMultitouchContacts[p].id = event.getPointerId(p);
|
||||
}
|
||||
for (int h = 0; h < historySize; h++) {
|
||||
for (int p = 0; p < pointerCount; p++) {
|
||||
mMultitouchContacts[p].x = (int)event.getHistoricalX(p, h);
|
||||
mMultitouchContacts[p].y = (int)event.getHistoricalY(p, h);
|
||||
}
|
||||
sendHidTouchReport(pointerCount);
|
||||
}
|
||||
for (int p = 0; p < pointerCount; p++) {
|
||||
mMultitouchContacts[p].x = (int)event.getX(p);
|
||||
mMultitouchContacts[p].y = (int)event.getY(p);
|
||||
}
|
||||
sendHidTouchReport(pointerCount);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
sendHidTouchReport(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHidTouchReport(int contactCount) {
|
||||
mHidBuffer.clear();
|
||||
mMultitouch.generateReport(mHidBuffer, mMultitouchContacts, contactCount);
|
||||
mHidBuffer.flip();
|
||||
|
||||
int count = mHidBuffer.limit();
|
||||
int len = mAccessoryConnection.controlTransfer(
|
||||
UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_SEND_HID_EVENT,
|
||||
MULTITOUCH_DEVICE_ID, 0,
|
||||
mHidBuffer.array(), 0, count, 10000);
|
||||
if (len != count) {
|
||||
mLogger.logError("Device rejected ACCESSORY_SEND_HID_EVENT request.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void startServices() {
|
||||
mDisplaySinkService = new DisplaySinkService(this, mTransport,
|
||||
getResources().getConfiguration().densityDpi);
|
||||
mDisplaySinkService.start();
|
||||
|
||||
if (mAttached) {
|
||||
mDisplaySinkService.setSurfaceView(mSurfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopServices() {
|
||||
if (mDisplaySinkService != null) {
|
||||
mDisplaySinkService.stop();
|
||||
mDisplaySinkService = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
mAttached = true;
|
||||
if (mDisplaySinkService != null) {
|
||||
mDisplaySinkService.setSurfaceView(mSurfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mAttached = false;
|
||||
if (mDisplaySinkService != null) {
|
||||
mDisplaySinkService.setSurfaceView(null);
|
||||
}
|
||||
}
|
||||
|
||||
private int getProtocol(UsbDeviceConnection conn) {
|
||||
byte buffer[] = new byte[2];
|
||||
int len = conn.controlTransfer(
|
||||
UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, 10000);
|
||||
if (len != 2) {
|
||||
return -1;
|
||||
}
|
||||
return (buffer[1] << 8) | buffer[0];
|
||||
}
|
||||
|
||||
private void sendString(UsbDeviceConnection conn, int index, String string) {
|
||||
byte[] buffer = (string + "\0").getBytes();
|
||||
int len = conn.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
|
||||
UsbAccessoryConstants.ACCESSORY_SEND_STRING, 0, index,
|
||||
buffer, buffer.length, 10000);
|
||||
if (len != buffer.length) {
|
||||
mLogger.logError("Failed to send string " + index + ": \"" + string + "\"");
|
||||
} else {
|
||||
mLogger.log("Sent string " + index + ": \"" + string + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAccessory(UsbDevice device) {
|
||||
final int vid = device.getVendorId();
|
||||
final int pid = device.getProductId();
|
||||
return vid == UsbAccessoryConstants.USB_ACCESSORY_VENDOR_ID
|
||||
&& (pid == UsbAccessoryConstants.USB_ACCESSORY_PRODUCT_ID
|
||||
|| pid == UsbAccessoryConstants.USB_ACCESSORY_ADB_PRODUCT_ID);
|
||||
}
|
||||
|
||||
class TextLogger extends Logger {
|
||||
@Override
|
||||
public void log(final String message) {
|
||||
Log.d(TAG, message);
|
||||
|
||||
mLogTextView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLogTextView.append(message);
|
||||
mLogTextView.append("\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
if (device != null) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
onDeviceAttached(device);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
onDeviceDetached(device);
|
||||
} else if (action.equals(ACTION_USB_DEVICE_PERMISSION)) {
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
mLogger.log("Device permission granted: " + device);
|
||||
onDeviceAttached(device);
|
||||
} else {
|
||||
mLogger.logError("Device permission denied: " + device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.sink;
|
||||
|
||||
import com.android.accessorydisplay.common.Logger;
|
||||
import com.android.accessorydisplay.common.Transport;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Sends or receives messages using bulk endpoints associated with a {@link UsbDevice}
|
||||
* that represents a USB accessory.
|
||||
*/
|
||||
public class UsbAccessoryBulkTransport extends Transport {
|
||||
private static final int TIMEOUT_MILLIS = 1000;
|
||||
|
||||
private UsbDeviceConnection mConnection;
|
||||
private UsbEndpoint mBulkInEndpoint;
|
||||
private UsbEndpoint mBulkOutEndpoint;
|
||||
|
||||
public UsbAccessoryBulkTransport(Logger logger, UsbDeviceConnection connection,
|
||||
UsbEndpoint bulkInEndpoint, UsbEndpoint bulkOutEndpoint) {
|
||||
super(logger, 16384);
|
||||
mConnection = connection;
|
||||
mBulkInEndpoint = bulkInEndpoint;
|
||||
mBulkOutEndpoint = bulkOutEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void ioClose() {
|
||||
mConnection = null;
|
||||
mBulkInEndpoint = null;
|
||||
mBulkOutEndpoint = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int ioRead(byte[] buffer, int offset, int count) throws IOException {
|
||||
if (mConnection == null) {
|
||||
throw new IOException("Connection was closed.");
|
||||
}
|
||||
return mConnection.bulkTransfer(mBulkInEndpoint, buffer, offset, count, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void ioWrite(byte[] buffer, int offset, int count) throws IOException {
|
||||
if (mConnection == null) {
|
||||
throw new IOException("Connection was closed.");
|
||||
}
|
||||
int result = mConnection.bulkTransfer(mBulkOutEndpoint,
|
||||
buffer, offset, count, TIMEOUT_MILLIS);
|
||||
if (result < 0) {
|
||||
throw new IOException("Bulk transfer failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.sink;
|
||||
|
||||
// Constants from kernel include/linux/usb/f_accessory.h
|
||||
final class UsbAccessoryConstants {
|
||||
/* Use Google Vendor ID when in accessory mode */
|
||||
public static final int USB_ACCESSORY_VENDOR_ID = 0x18D1;
|
||||
|
||||
/* Product ID to use when in accessory mode */
|
||||
public static final int USB_ACCESSORY_PRODUCT_ID = 0x2D00;
|
||||
|
||||
/* Product ID to use when in accessory mode and adb is enabled */
|
||||
public static final int USB_ACCESSORY_ADB_PRODUCT_ID = 0x2D01;
|
||||
|
||||
/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */
|
||||
public static final int ACCESSORY_STRING_MANUFACTURER = 0;
|
||||
public static final int ACCESSORY_STRING_MODEL = 1;
|
||||
public static final int ACCESSORY_STRING_DESCRIPTION = 2;
|
||||
public static final int ACCESSORY_STRING_VERSION = 3;
|
||||
public static final int ACCESSORY_STRING_URI = 4;
|
||||
public static final int ACCESSORY_STRING_SERIAL = 5;
|
||||
|
||||
/* Control request for retrieving device's protocol version
|
||||
*
|
||||
* requestType: USB_DIR_IN | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_GET_PROTOCOL
|
||||
* value: 0
|
||||
* index: 0
|
||||
* data version number (16 bits little endian)
|
||||
* 1 for original accessory support
|
||||
* 2 adds HID and device to host audio support
|
||||
*/
|
||||
public static final int ACCESSORY_GET_PROTOCOL = 51;
|
||||
|
||||
/* Control request for host to send a string to the device
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_SEND_STRING
|
||||
* value: 0
|
||||
* index: string ID
|
||||
* data zero terminated UTF8 string
|
||||
*
|
||||
* The device can later retrieve these strings via the
|
||||
* ACCESSORY_GET_STRING_* ioctls
|
||||
*/
|
||||
public static final int ACCESSORY_SEND_STRING = 52;
|
||||
|
||||
/* Control request for starting device in accessory mode.
|
||||
* The host sends this after setting all its strings to the device.
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_START
|
||||
* value: 0
|
||||
* index: 0
|
||||
* data none
|
||||
*/
|
||||
public static final int ACCESSORY_START = 53;
|
||||
|
||||
/* Control request for registering a HID device.
|
||||
* Upon registering, a unique ID is sent by the accessory in the
|
||||
* value parameter. This ID will be used for future commands for
|
||||
* the device
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_REGISTER_HID_DEVICE
|
||||
* value: Accessory assigned ID for the HID device
|
||||
* index: total length of the HID report descriptor
|
||||
* data none
|
||||
*/
|
||||
public static final int ACCESSORY_REGISTER_HID = 54;
|
||||
|
||||
/* Control request for unregistering a HID device.
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_REGISTER_HID
|
||||
* value: Accessory assigned ID for the HID device
|
||||
* index: 0
|
||||
* data none
|
||||
*/
|
||||
public static final int ACCESSORY_UNREGISTER_HID = 55;
|
||||
|
||||
/* Control request for sending the HID report descriptor.
|
||||
* If the HID descriptor is longer than the endpoint zero max packet size,
|
||||
* the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
|
||||
* commands. The data for the descriptor must be sent sequentially
|
||||
* if multiple packets are needed.
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_SET_HID_REPORT_DESC
|
||||
* value: Accessory assigned ID for the HID device
|
||||
* index: offset of data in descriptor
|
||||
* (needed when HID descriptor is too big for one packet)
|
||||
* data the HID report descriptor
|
||||
*/
|
||||
public static final int ACCESSORY_SET_HID_REPORT_DESC = 56;
|
||||
|
||||
/* Control request for sending HID events.
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_SEND_HID_EVENT
|
||||
* value: Accessory assigned ID for the HID device
|
||||
* index: 0
|
||||
* data the HID report for the event
|
||||
*/
|
||||
public static final int ACCESSORY_SEND_HID_EVENT = 57;
|
||||
|
||||
/* Control request for setting the audio mode.
|
||||
*
|
||||
* requestType: USB_DIR_OUT | USB_TYPE_VENDOR
|
||||
* request: ACCESSORY_SET_AUDIO_MODE
|
||||
* value: 0 - no audio
|
||||
* 1 - device to host, 44100 16-bit stereo PCM
|
||||
* index: 0
|
||||
* data none
|
||||
*/
|
||||
public static final int ACCESSORY_SET_AUDIO_MODE = 58;
|
||||
|
||||
private UsbAccessoryConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.accessorydisplay.sink;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Helper for creating USB HID descriptors and reports.
|
||||
*/
|
||||
final class UsbHid {
|
||||
private UsbHid() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates basic Windows 7 compatible HID multitouch descriptors and reports
|
||||
* that should be supported by recent versions of the Linux hid-multitouch driver.
|
||||
*/
|
||||
public static final class Multitouch {
|
||||
private final int mReportId;
|
||||
private final int mMaxContacts;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
|
||||
public Multitouch(int reportId, int maxContacts, int width, int height) {
|
||||
mReportId = reportId;
|
||||
mMaxContacts = maxContacts;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
public void generateDescriptor(ByteBuffer buffer) {
|
||||
buffer.put(new byte[] {
|
||||
0x05, 0x0d, // USAGE_PAGE (Digitizers)
|
||||
0x09, 0x04, // USAGE (Touch Screen)
|
||||
(byte)0xa1, 0x01, // COLLECTION (Application)
|
||||
(byte)0x85, (byte)mReportId, // REPORT_ID (Touch)
|
||||
0x09, 0x22, // USAGE (Finger)
|
||||
(byte)0xa1, 0x00, // COLLECTION (Physical)
|
||||
0x09, 0x55, // USAGE (Contact Count Maximum)
|
||||
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||
0x25, (byte)mMaxContacts, // LOGICAL_MAXIMUM (...)
|
||||
0x75, 0x08, // REPORT_SIZE (8)
|
||||
(byte)0x95, 0x01, // REPORT_COUNT (1)
|
||||
(byte)0xb1, (byte)mMaxContacts, // FEATURE (Data,Var,Abs)
|
||||
0x09, 0x54, // USAGE (Contact Count)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
});
|
||||
byte maxXLsb = (byte)(mWidth - 1);
|
||||
byte maxXMsb = (byte)((mWidth - 1) >> 8);
|
||||
byte maxYLsb = (byte)(mHeight - 1);
|
||||
byte maxYMsb = (byte)((mHeight - 1) >> 8);
|
||||
byte[] collection = new byte[] {
|
||||
0x05, 0x0d, // USAGE_PAGE (Digitizers)
|
||||
0x09, 0x22, // USAGE (Finger)
|
||||
(byte)0xa1, 0x02, // COLLECTION (Logical)
|
||||
0x09, 0x42, // USAGE (Tip Switch)
|
||||
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||
0x25, 0x01, // LOGICAL_MAXIMUM (1)
|
||||
0x75, 0x01, // REPORT_SIZE (1)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
0x09, 0x32, // USAGE (In Range)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
0x09, 0x51, // USAGE (Contact Identifier)
|
||||
0x25, 0x3f, // LOGICAL_MAXIMUM (63)
|
||||
0x75, 0x06, // REPORT_SIZE (6)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||
0x09, 0x30, // USAGE (X)
|
||||
0x26, maxXLsb, maxXMsb, // LOGICAL_MAXIMUM (...)
|
||||
0x75, 0x10, // REPORT_SIZE (16)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
0x09, 0x31, // USAGE (Y)
|
||||
0x26, maxYLsb, maxYMsb, // LOGICAL_MAXIMUM (...)
|
||||
(byte)0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
(byte)0xc0, // END_COLLECTION
|
||||
};
|
||||
for (int i = 0; i < mMaxContacts; i++) {
|
||||
buffer.put(collection);
|
||||
}
|
||||
buffer.put(new byte[] {
|
||||
(byte)0xc0, // END_COLLECTION
|
||||
(byte)0xc0, // END_COLLECTION
|
||||
});
|
||||
}
|
||||
|
||||
public void generateReport(ByteBuffer buffer, Contact[] contacts, int contactCount) {
|
||||
// Report Id
|
||||
buffer.put((byte)mReportId);
|
||||
// Contact Count
|
||||
buffer.put((byte)contactCount);
|
||||
|
||||
for (int i = 0; i < contactCount; i++) {
|
||||
final Contact contact = contacts[i];
|
||||
// Tip Switch, In Range, Contact Identifier
|
||||
buffer.put((byte)((contact.id << 2) | 0x03));
|
||||
// X
|
||||
buffer.put((byte)contact.x).put((byte)(contact.x >> 8));
|
||||
// Y
|
||||
buffer.put((byte)contact.y).put((byte)(contact.y >> 8));
|
||||
}
|
||||
for (int i = contactCount; i < mMaxContacts; i++) {
|
||||
buffer.put((byte)0).put((byte)0).put((byte)0).put((byte)0).put((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
public int getReportSize() {
|
||||
return 2 + mMaxContacts * 5;
|
||||
}
|
||||
|
||||
public static final class Contact {
|
||||
public int id; // range 0..63
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
tests/AccessoryDisplay/source/Android.mk
Normal file
25
tests/AccessoryDisplay/source/Android.mk
Normal file
@@ -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)
|
||||
41
tests/AccessoryDisplay/source/AndroidManifest.xml
Normal file
41
tests/AccessoryDisplay/source/AndroidManifest.xml
Normal file
@@ -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>
|
||||
BIN
tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
Executable file
BIN
tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
Normal file
BIN
tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -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>
|
||||
24
tests/AccessoryDisplay/source/res/layout/source_activity.xml
Normal file
24
tests/AccessoryDisplay/source/res/layout/source_activity.xml
Normal file
@@ -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>
|
||||
19
tests/AccessoryDisplay/source/res/values/strings.xml
Normal file
19
tests/AccessoryDisplay/source/res/values/strings.xml
Normal file
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user