summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt17
-rw-r--r--core/java/android/hardware/display/DisplayManager.java37
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java50
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl8
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java62
-rw-r--r--core/java/android/provider/ContactsContract.java139
-rw-r--r--core/java/android/view/Display.java69
-rw-r--r--core/java/android/view/DisplayInfo.java47
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/webkit/WebViewClassic.java14
-rw-r--r--core/java/android/webkit/WebViewFactory.java79
-rw-r--r--core/java/android/widget/HorizontalScrollView.java18
-rw-r--r--core/java/android/widget/ScrollView.java10
-rw-r--r--docs/html/guide/appendix/media-formats.jd65
-rw-r--r--graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java5
-rw-r--r--graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java7
-rw-r--r--graphics/java/android/renderscript/ScriptIntrinsicHistogram.java186
-rw-r--r--packages/Keyguard/AndroidManifest.xml2
-rw-r--r--preloaded-classes3
-rw-r--r--services/java/com/android/server/am/ActiveServices.java1
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java44
-rw-r--r--services/java/com/android/server/am/ActivityStackSupervisor.java10
-rw-r--r--services/java/com/android/server/display/DisplayDeviceInfo.java68
-rw-r--r--services/java/com/android/server/display/DisplayManagerService.java265
-rw-r--r--services/java/com/android/server/display/LogicalDisplay.java5
-rw-r--r--services/java/com/android/server/display/VirtualDisplayAdapter.java161
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java7
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java19
-rw-r--r--tests/AccessoryDisplay/Android.mk17
-rw-r--r--tests/AccessoryDisplay/README50
-rw-r--r--tests/AccessoryDisplay/common/Android.mk23
-rw-r--r--tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/BufferPool.java92
-rw-r--r--tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Logger.java25
-rw-r--r--tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Protocol.java65
-rw-r--r--tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Service.java71
-rw-r--r--tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Transport.java382
-rw-r--r--tests/AccessoryDisplay/sink/Android.mk25
-rw-r--r--tests/AccessoryDisplay/sink/AndroidManifest.xml41
-rwxr-xr-xtests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.pngbin0 -> 3608 bytes
-rw-r--r--tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.pngbin0 -> 5198 bytes
-rw-r--r--tests/AccessoryDisplay/sink/res/layout/sink_activity.xml44
-rw-r--r--tests/AccessoryDisplay/sink/res/values/strings.xml19
-rw-r--r--tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml25
-rw-r--r--tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/DisplaySinkService.java240
-rw-r--r--tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java508
-rw-r--r--tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryBulkTransport.java73
-rw-r--r--tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryConstants.java135
-rw-r--r--tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbHid.java130
-rw-r--r--tests/AccessoryDisplay/source/Android.mk25
-rw-r--r--tests/AccessoryDisplay/source/AndroidManifest.xml41
-rwxr-xr-xtests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.pngbin0 -> 3608 bytes
-rw-r--r--tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.pngbin0 -> 5198 bytes
-rw-r--r--tests/AccessoryDisplay/source/res/layout/presentation_content.xml30
-rw-r--r--tests/AccessoryDisplay/source/res/layout/source_activity.xml24
-rw-r--r--tests/AccessoryDisplay/source/res/values/strings.xml19
-rw-r--r--tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml20
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java246
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java257
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java70
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java100
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java124
-rw-r--r--tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java84
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java6
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java41
64 files changed, 4117 insertions, 338 deletions
diff --git a/api/current.txt b/api/current.txt
index 025bb35..e3d0674 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10548,6 +10548,7 @@ package android.hardware {
package android.hardware.display {
public final class DisplayManager {
+ method public android.hardware.display.VirtualDisplay createPrivateVirtualDisplay(java.lang.String, int, int, int, android.view.Surface);
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(java.lang.String);
@@ -10562,6 +10563,11 @@ package android.hardware.display {
method public abstract void onDisplayRemoved(int);
}
+ public final class VirtualDisplay {
+ method public android.view.Display getDisplay();
+ method public void release();
+ }
+
}
package android.hardware.input {
@@ -21327,6 +21333,16 @@ package android.renderscript {
method public void setInput(android.renderscript.Allocation);
}
+ public final class ScriptIntrinsicHistogram extends android.renderscript.ScriptIntrinsic {
+ method public static android.renderscript.ScriptIntrinsicHistogram create(android.renderscript.RenderScript, android.renderscript.Element);
+ method public void forEach(android.renderscript.Allocation);
+ method public void forEach_dot(android.renderscript.Allocation);
+ method public android.renderscript.Script.FieldID getFieldID_Input();
+ method public android.renderscript.Script.KernelID getKernelID_seperate();
+ method public void setDotCoefficients(float, float, float, float);
+ method public void setOutput(android.renderscript.Allocation);
+ }
+
public final class ScriptIntrinsicLUT extends android.renderscript.ScriptIntrinsic {
method public static android.renderscript.ScriptIntrinsicLUT create(android.renderscript.RenderScript, android.renderscript.Element);
method public void forEach(android.renderscript.Allocation, android.renderscript.Allocation);
@@ -25050,6 +25066,7 @@ package android.view {
method public deprecated int getWidth();
method public boolean isValid();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
+ field public static final int FLAG_PRIVATE = 4; // 0x4
field public static final int FLAG_SECURE = 2; // 0x2
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0a7a2e7..dcf50cd 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Display;
+import android.view.Surface;
import java.util.ArrayList;
@@ -134,6 +135,7 @@ public final class DisplayManager {
addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI);
addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
+ addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
}
return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
} finally {
@@ -275,6 +277,41 @@ public final class DisplayManager {
}
/**
+ * Creates a private virtual display.
+ * <p>
+ * The content of a virtual display is rendered to a {@link Surface} provided
+ * by the application that created the virtual display.
+ * </p><p>
+ * Only the application that created a private virtual display is allowed to
+ * place windows upon it. The private virtual display also does not participate
+ * in display mirroring: it will neither receive mirrored content from another
+ * display nor allow its own content to be mirrored elsewhere. More precisely,
+ * the only processes that are allowed to enumerate or interact with a private
+ * display are those that have the same UID as the application that originally
+ * created the private virtual display.
+ * </p><p>
+ * The private virtual display should be {@link VirtualDisplay#release released}
+ * when no longer needed. Because a private virtual display renders to a surface
+ * provided by the application, it will be released automatically when the
+ * process terminates and all remaining windows on it will be forcibly removed.
+ * </p>
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels, must be greater than 0.
+ * @param height The height of the virtual display in pixels, must be greater than 0.
+ * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
+ * @param surface The surface to which the content of the virtual display should
+ * be rendered, must be non-null.
+ * @return The newly created virtual display, or null if the application could
+ * not create the virtual display.
+ */
+ public VirtualDisplay createPrivateVirtualDisplay(String name,
+ int width, int height, int densityDpi, Surface surface) {
+ return mGlobal.createPrivateVirtualDisplay(mContext,
+ name, width, height, densityDpi, surface);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a858681..3ab882d 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,17 +18,20 @@ package android.hardware.display;
import android.content.Context;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.CompatibilityInfoHolder;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import java.util.ArrayList;
@@ -315,6 +318,53 @@ public final class DisplayManagerGlobal {
}
}
+ public VirtualDisplay createPrivateVirtualDisplay(Context context, String name,
+ int width, int height, int densityDpi, Surface surface) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must be non-null and non-empty");
+ }
+ if (width <= 0 || height <= 0 || densityDpi <= 0) {
+ throw new IllegalArgumentException("width, height, and densityDpi must be "
+ + "greater than 0");
+ }
+ if (surface == null) {
+ throw new IllegalArgumentException("surface must not be null");
+ }
+
+ Binder token = new Binder();
+ int displayId;
+ try {
+ displayId = mDm.createPrivateVirtualDisplay(token, context.getPackageName(),
+ name, width, height, densityDpi, surface);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not create private virtual display: " + name, ex);
+ return null;
+ }
+ if (displayId < 0) {
+ Log.e(TAG, "Could not create private virtual display: " + name);
+ return null;
+ }
+ Display display = getRealDisplay(displayId);
+ if (display == null) {
+ Log.wtf(TAG, "Could not obtain display info for newly created "
+ + "private virtual display: " + name);
+ try {
+ mDm.releaseVirtualDisplay(token);
+ } catch (RemoteException ex) {
+ }
+ return null;
+ }
+ return new VirtualDisplay(this, display, token);
+ }
+
+ public void releaseVirtualDisplay(IBinder token) {
+ try {
+ mDm.releaseVirtualDisplay(token);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to release virtual display.", ex);
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 79aad78..cd4896a 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -20,6 +20,7 @@ import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplayStatus;
import android.view.DisplayInfo;
+import android.view.Surface;
/** @hide */
interface IDisplayManager {
@@ -46,4 +47,11 @@ interface IDisplayManager {
// No permissions required.
WifiDisplayStatus getWifiDisplayStatus();
+
+ // No permissions required.
+ int createPrivateVirtualDisplay(IBinder token, String packageName,
+ String name, int width, int height, int densityDpi, in Surface surface);
+
+ // No permissions required but must be same Uid as the creator.
+ void releaseVirtualDisplay(in IBinder token);
}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
new file mode 100644
index 0000000..145a217
--- /dev/null
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.hardware.display;
+
+import android.os.IBinder;
+import android.view.Display;
+
+/**
+ * Represents a virtual display.
+ *
+ * @see DisplayManager#createPrivateVirtualDisplay
+ */
+public final class VirtualDisplay {
+ private final DisplayManagerGlobal mGlobal;
+ private final Display mDisplay;
+ private IBinder mToken;
+
+ VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token) {
+ mGlobal = global;
+ mDisplay = display;
+ mToken = token;
+ }
+
+ /**
+ * Gets the virtual display.
+ */
+ public Display getDisplay() {
+ return mDisplay;
+ }
+
+ /**
+ * Releases the virtual display and destroys its underlying surface.
+ * <p>
+ * All remaining windows on the virtual display will be forcibly removed
+ * as part of releasing the virtual display.
+ * </p>
+ */
+ public void release() {
+ if (mToken != null) {
+ mGlobal.releaseVirtualDisplay(mToken);
+ mToken = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + "}";
+ }
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 220b997..e59f5b1 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -5105,9 +5105,8 @@ public final class ContactsContract {
* Value of 1 implies true, 0 implies false when 0 is the default.
* When a cursor is returned to the client, it should check for an extra with the name
* {@link ContactsContract#DEFERRED_SNIPPETING} in the cursor. If it exists, the client
- * should do its own snippeting using {@link ContactsContract#snippetize}. If
- * it doesn't exist, the snippet column in the cursor should already contain a snippetized
- * string.
+ * should do its own snippeting. If it doesn't exist, the snippet column in the cursor
+ * should already contain a snippetized string.
*
* @hide
*/
@@ -8463,138 +8462,4 @@ public final class ContactsContract {
public static final String DATA_SET = "com.android.contacts.extra.DATA_SET";
}
}
-
- /**
- * Creates a snippet out of the given content that matches the given query.
- * @param content - The content to use to compute the snippet.
- * @param displayName - Display name for the contact - if this already contains the search
- * content, no snippet should be shown.
- * @param query - String to search for in the content.
- * @param snippetStartMatch - Marks the start of the matching string in the snippet.
- * @param snippetEndMatch - Marks the end of the matching string in the snippet.
- * @param snippetEllipsis - Ellipsis string appended to the end of the snippet (if too long).
- * @param snippetMaxTokens - Maximum number of words from the snippet that will be displayed.
- * @return The computed snippet, or null if the snippet could not be computed or should not be
- * shown.
- *
- * @hide
- */
- public static String snippetize(String content, String displayName, String query,
- char snippetStartMatch, char snippetEndMatch, String snippetEllipsis,
- int snippetMaxTokens) {
-
- String lowerQuery = query != null ? query.toLowerCase() : null;
- if (TextUtils.isEmpty(content) || TextUtils.isEmpty(query) ||
- TextUtils.isEmpty(displayName) || !content.toLowerCase().contains(lowerQuery)) {
- return null;
- }
-
- // If the display name already contains the query term, return empty - snippets should
- // not be needed in that case.
- String lowerDisplayName = displayName != null ? displayName.toLowerCase() : "";
- List<String> nameTokens = new ArrayList<String>();
- List<Integer> nameTokenOffsets = new ArrayList<Integer>();
- split(lowerDisplayName.trim(), nameTokens, nameTokenOffsets);
- for (String nameToken : nameTokens) {
- if (nameToken.startsWith(lowerQuery)) {
- return null;
- }
- }
-
- String[] contentLines = content.split("\n");
-
- // Locate the lines of the content that contain the query term.
- for (String contentLine : contentLines) {
- if (contentLine.toLowerCase().contains(lowerQuery)) {
-
- // Line contains the query string - now search for it at the start of tokens.
- List<String> lineTokens = new ArrayList<String>();
- List<Integer> tokenOffsets = new ArrayList<Integer>();
- split(contentLine, lineTokens, tokenOffsets);
-
- // As we find matches against the query, we'll populate this list with the marked
- // (or unchanged) tokens.
- List<String> markedTokens = new ArrayList<String>();
-
- int firstToken = -1;
- int lastToken = -1;
- for (int i = 0; i < lineTokens.size(); i++) {
- String token = lineTokens.get(i);
- String lowerToken = token.toLowerCase();
- if (lowerToken.startsWith(lowerQuery)) {
-
- // Query term matched; surround the token with match markers.
- markedTokens.add(snippetStartMatch + token + snippetEndMatch);
-
- // If this is the first token found with a match, mark the token
- // positions to use for assembling the snippet.
- if (firstToken == -1) {
- firstToken =
- Math.max(0, i - (int) Math.floor(
- Math.abs(snippetMaxTokens)
- / 2.0));
- lastToken =
- Math.min(lineTokens.size(), firstToken +
- Math.abs(snippetMaxTokens));
- }
- } else {
- markedTokens.add(token);
- }
- }
-
- // Assemble the snippet by piecing the tokens back together.
- if (firstToken > -1) {
- StringBuilder sb = new StringBuilder();
- if (firstToken > 0) {
- sb.append(snippetEllipsis);
- }
- for (int i = firstToken; i < lastToken; i++) {
- String markedToken = markedTokens.get(i);
- String originalToken = lineTokens.get(i);
- sb.append(markedToken);
- if (i < lastToken - 1) {
- // Add the characters that appeared between this token and the next.
- sb.append(contentLine.substring(
- tokenOffsets.get(i) + originalToken.length(),
- tokenOffsets.get(i + 1)));
- }
- }
- if (lastToken < lineTokens.size()) {
- sb.append(snippetEllipsis);
- }
- return sb.toString();
- }
- }
- }
- return null;
- }
-
- /**
- * Pattern for splitting a line into tokens. This matches e-mail addresses as a single token,
- * otherwise splitting on any group of non-alphanumeric characters.
- *
- * @hide
- */
- private static Pattern SPLIT_PATTERN =
- Pattern.compile("([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
-
- /**
- * Helper method for splitting a string into tokens. The lists passed in are populated with the
- * tokens and offsets into the content of each token. The tokenization function parses e-mail
- * addresses as a single token; otherwise it splits on any non-alphanumeric character.
- * @param content Content to split.
- * @param tokens List of token strings to populate.
- * @param offsets List of offsets into the content for each token returned.
- *
- * @hide
- */
- private static void split(String content, List<String> tokens, List<Integer> offsets) {
- Matcher matcher = SPLIT_PATTERN.matcher(content);
- while (matcher.find()) {
- tokens.add(matcher.group());
- offsets.add(matcher.start());
- }
- }
-
-
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index e6a7950..4d984fd 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -20,6 +20,7 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.Process;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -57,6 +58,8 @@ public final class Display {
private final int mFlags;
private final int mType;
private final String mAddress;
+ private final int mOwnerUid;
+ private final String mOwnerPackageName;
private final CompatibilityInfoHolder mCompatibilityInfo;
private DisplayInfo mDisplayInfo; // never null
@@ -143,6 +146,18 @@ public final class Display {
public static final int FLAG_SECURE = 1 << 1;
/**
+ * Display flag: Indicates that the display is private. Only the application that
+ * owns the display can create windows on it.
+ * <p>
+ * This flag is associated with displays that were created using
+ * {@link android.hardware.display.DisplayManager#createPrivateVirtualDisplay}.
+ * </p>
+ *
+ * @see #getFlags
+ */
+ public static final int FLAG_PRIVATE = 1 << 2;
+
+ /**
* Display type: Unknown display type.
* @hide
*/
@@ -173,6 +188,12 @@ public final class Display {
public static final int TYPE_OVERLAY = 4;
/**
+ * Display type: Virtual display.
+ * @hide
+ */
+ public static final int TYPE_VIRTUAL = 5;
+
+ /**
* Internal method to create a display.
* Applications should use {@link android.view.WindowManager#getDefaultDisplay()}
* or {@link android.hardware.display.DisplayManager#getDisplay}
@@ -194,6 +215,8 @@ public final class Display {
mFlags = displayInfo.flags;
mType = displayInfo.type;
mAddress = displayInfo.address;
+ mOwnerUid = displayInfo.ownerUid;
+ mOwnerPackageName = displayInfo.ownerPackageName;
}
/**
@@ -262,6 +285,7 @@ public final class Display {
*
* @see #FLAG_SUPPORTS_PROTECTED_BUFFERS
* @see #FLAG_SECURE
+ * @see #FLAG_PRIVATE
*/
public int getFlags() {
return mFlags;
@@ -277,6 +301,7 @@ public final class Display {
* @see #TYPE_HDMI
* @see #TYPE_WIFI
* @see #TYPE_OVERLAY
+ * @see #TYPE_VIRTUAL
* @hide
*/
public int getType() {
@@ -295,6 +320,32 @@ public final class Display {
}
/**
+ * Gets the UID of the application that owns this display, or zero if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ *
+ * @hide
+ */
+ public int getOwnerUid() {
+ return mOwnerUid;
+ }
+
+ /**
+ * Gets the package name of the application that owns this display, or null if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ *
+ * @hide
+ */
+ public String getOwnerPackageName() {
+ return mOwnerPackageName;
+ }
+
+ /**
* Gets the compatibility info used by this display instance.
*
* @return The compatibility info holder, or null if none is required.
@@ -564,6 +615,22 @@ public final class Display {
}
}
+ /**
+ * Returns true if the specified UID has access to this display.
+ * @hide
+ */
+ public boolean hasAccess(int uid) {
+ return Display.hasAccess(uid, mFlags, mOwnerUid);
+ }
+
+ /** @hide */
+ public static boolean hasAccess(int uid, int flags, int ownerUid) {
+ return (flags & Display.FLAG_PRIVATE) == 0
+ || uid == ownerUid
+ || uid == Process.SYSTEM_UID
+ || uid == 0;
+ }
+
private void updateDisplayInfoLocked() {
// Note: The display manager caches display info objects on our behalf.
DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
@@ -624,6 +691,8 @@ public final class Display {
return "WIFI";
case TYPE_OVERLAY:
return "OVERLAY";
+ case TYPE_VIRTUAL:
+ return "VIRTUAL";
default:
return Integer.toString(type);
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 9fcd9b1..1442cb7 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -19,6 +19,7 @@ package android.view;
import android.content.res.CompatibilityInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.util.DisplayMetrics;
import libcore.util.Objects;
@@ -177,6 +178,23 @@ public final class DisplayInfo implements Parcelable {
*/
public float physicalYDpi;
+ /**
+ * The UID of the application that owns this display, or zero if it is owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public int ownerUid;
+
+ /**
+ * The package name of the application that owns this display, or null if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public String ownerPackageName;
+
public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -228,7 +246,9 @@ public final class DisplayInfo implements Parcelable {
&& refreshRate == other.refreshRate
&& logicalDensityDpi == other.logicalDensityDpi
&& physicalXDpi == other.physicalXDpi
- && physicalYDpi == other.physicalYDpi;
+ && physicalYDpi == other.physicalYDpi
+ && ownerUid == other.ownerUid
+ && Objects.equal(ownerPackageName, other.ownerPackageName);
}
@Override
@@ -259,6 +279,8 @@ public final class DisplayInfo implements Parcelable {
logicalDensityDpi = other.logicalDensityDpi;
physicalXDpi = other.physicalXDpi;
physicalYDpi = other.physicalYDpi;
+ ownerUid = other.ownerUid;
+ ownerPackageName = other.ownerPackageName;
}
public void readFromParcel(Parcel source) {
@@ -284,6 +306,8 @@ public final class DisplayInfo implements Parcelable {
logicalDensityDpi = source.readInt();
physicalXDpi = source.readFloat();
physicalYDpi = source.readFloat();
+ ownerUid = source.readInt();
+ ownerPackageName = source.readString();
}
@Override
@@ -310,6 +334,8 @@ public final class DisplayInfo implements Parcelable {
dest.writeInt(logicalDensityDpi);
dest.writeFloat(physicalXDpi);
dest.writeFloat(physicalYDpi);
+ dest.writeInt(ownerUid);
+ dest.writeString(ownerPackageName);
}
@Override
@@ -335,6 +361,13 @@ public final class DisplayInfo implements Parcelable {
logicalHeight : logicalWidth;
}
+ /**
+ * Returns true if the specified UID has access to this display.
+ */
+ public boolean hasAccess(int uid) {
+ return Display.hasAccess(uid, flags, ownerUid);
+ }
+
private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih,
int width, int height) {
outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
@@ -402,8 +435,13 @@ public final class DisplayInfo implements Parcelable {
sb.append(layerStack);
sb.append(", type ");
sb.append(Display.typeToString(type));
- sb.append(", address ");
- sb.append(address);
+ if (address != null) {
+ sb.append(", address ").append(address);
+ }
+ if (ownerUid != 0 || ownerPackageName != null) {
+ sb.append(", owner ").append(ownerPackageName);
+ sb.append(" (uid ").append(ownerUid).append(")");
+ }
sb.append(flagsToString(flags));
sb.append("}");
return sb.toString();
@@ -417,6 +455,9 @@ public final class DisplayInfo implements Parcelable {
if ((flags & Display.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
result.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS");
}
+ if ((flags & Display.FLAG_PRIVATE) != 0) {
+ result.append(", FLAG_PRIVATE");
+ }
return result.toString();
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 109b8e0..e62abbe 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6442,7 +6442,10 @@ public final class ViewRootImpl implements ViewParent,
public void runOrPost(View source) {
if (mSource != null) {
- mSource = getCommonPredecessor(mSource, source);
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ View predecessor = getCommonPredecessor(mSource, source);
+ mSource = (predecessor != null) ? predecessor : source;
return;
}
mSource = source;
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index a324502..3afab09 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -66,6 +66,7 @@ import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -1293,6 +1294,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// WebViewProvider bindings
static class Factory implements WebViewFactoryProvider, WebViewFactoryProvider.Statics {
+ Factory() {
+ // Touch JniUtil and WebViewCore in case this is being called from
+ // WebViewFactory.Preloader, to ensure that the JNI libraries that they use are
+ // preloaded in the zygote.
+ try {
+ Class.forName("android.webkit.JniUtil");
+ Class.forName("android.webkit.WebViewCore");
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "failed to load JNI libraries");
+ throw new AndroidRuntimeException(e);
+ }
+ }
+
@Override
public String findAddress(String addr) {
return WebViewClassic.findAddress(addr);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index ea5187a..2ee0961 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -19,10 +19,9 @@ package android.webkit;
import android.os.Build;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.util.AndroidRuntimeException;
import android.util.Log;
-import dalvik.system.PathClassLoader;
-
/**
* Top level factory, used creating all the main WebView implementation classes.
*
@@ -45,6 +44,17 @@ public final class WebViewFactory {
private static final boolean DEBUG = false;
+ private static class Preloader {
+ static WebViewFactoryProvider sPreloadedProvider;
+ static {
+ try {
+ sPreloadedProvider = getFactoryClass().newInstance();
+ } catch (Exception e) {
+ Log.w(LOGTAG, "error preloading provider", e);
+ }
+ }
+ }
+
// Cache the factory both for efficiency, and ensure any one process gets all webviews from the
// same provider.
private static WebViewFactoryProvider sProviderInstance;
@@ -67,32 +77,39 @@ public final class WebViewFactory {
// us honest and minimize usage of WebViewClassic internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
- if (isExperimentalWebViewEnabled()) {
- StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
- try {
- sProviderInstance = getFactoryByName(CHROMIUM_WEBVIEW_FACTORY);
- if (DEBUG) Log.v(LOGTAG, "Loaded Chromium provider: " + sProviderInstance);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- }
+ Class<WebViewFactoryProvider> providerClass;
+ try {
+ providerClass = getFactoryClass();
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "error loading provider", e);
+ throw new AndroidRuntimeException(e);
}
- if (sProviderInstance == null) {
- if (DEBUG) Log.v(LOGTAG, "Falling back to default provider: "
- + DEFAULT_WEBVIEW_FACTORY);
- sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY);
- if (sProviderInstance == null) {
- if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
- sProviderInstance = new WebViewClassic.Factory();
- }
+ // This implicitly loads Preloader even if it wasn't preloaded at boot.
+ if (Preloader.sPreloadedProvider != null &&
+ Preloader.sPreloadedProvider.getClass() == providerClass) {
+ sProviderInstance = Preloader.sPreloadedProvider;
+ if (DEBUG) Log.v(LOGTAG, "Using preloaded provider: " + sProviderInstance);
+ return sProviderInstance;
+ }
+
+ // The preloaded provider isn't the one we wanted; construct our own.
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ sProviderInstance = providerClass.newInstance();
+ if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
+ return sProviderInstance;
+ } catch (Exception e) {
+ Log.e(LOGTAG, "error instantiating provider", e);
+ throw new AndroidRuntimeException(e);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
- return sProviderInstance;
}
}
- // For debug builds, we allow a system property to specify that we should use the
- // experimtanl Chromium powered WebView. This enables us to switch between
- // implementations at runtime. For user (release) builds, don't allow this.
+ // We allow a system property to specify that we should use the experimental Chromium powered
+ // WebView. This enables us to switch between implementations at runtime.
private static boolean isExperimentalWebViewEnabled() {
if (!isExperimentalWebViewAvailable()) return false;
boolean use_experimental_webview = SystemProperties.getBoolean(
@@ -108,19 +125,11 @@ public final class WebViewFactory {
return use_experimental_webview;
}
- private static WebViewFactoryProvider getFactoryByName(String providerName) {
- try {
- if (DEBUG) Log.v(LOGTAG, "attempt to load class " + providerName);
- Class<?> c = Class.forName(providerName);
- if (DEBUG) Log.v(LOGTAG, "instantiating factory");
- return (WebViewFactoryProvider) c.newInstance();
- } catch (ClassNotFoundException e) {
- Log.e(LOGTAG, "error loading " + providerName, e);
- } catch (IllegalAccessException e) {
- Log.e(LOGTAG, "error loading " + providerName, e);
- } catch (InstantiationException e) {
- Log.e(LOGTAG, "error loading " + providerName, e);
+ private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
+ if (isExperimentalWebViewEnabled()) {
+ return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
+ } else {
+ return (Class<WebViewFactoryProvider>) Class.forName(DEFAULT_WEBVIEW_FACTORY);
}
- return null;
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index f0d80e6..d114b76 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1457,14 +1457,22 @@ public class HorizontalScrollView extends FrameLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // There is only one child
- final View child = getChildAt(0);
- final int childWidth = child.getMeasuredWidth();
- final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
+ int childWidth = 0;
+ int childMargins = 0;
+
+ if (getChildCount() > 0) {
+ childWidth = getChildAt(0).getMeasuredWidth();
+ LayoutParams childParams = (LayoutParams) getChildAt(0).getLayoutParams();
+ childMargins = childParams.leftMargin + childParams.rightMargin;
+ }
+
final int available = r - l - getPaddingLeftWithForeground() -
- getPaddingRightWithForeground() - childParams.leftMargin - childParams.rightMargin;
+ getPaddingRightWithForeground() - childMargins;
+
final boolean forceLeftGravity = (childWidth > available);
+
layoutChildren(l, t, r, b, forceLeftGravity);
+
mIsLayoutDirty = false;
// Give a child focus if it needs it
if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index b32cfbc..3d361f1 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1473,16 +1473,16 @@ public class ScrollView extends FrameLayout {
}
mChildToScrollTo = null;
- // There is only one child
- final View child = getChildAt(0);
- final int childHeight = child.getMeasuredHeight();
if (!hasLayout()) {
- final int scrollRange = Math.max(0,
- childHeight - (b - t - mPaddingBottom - mPaddingTop));
if (mSavedState != null) {
mScrollY = mSavedState.scrollPosition;
mSavedState = null;
} // mScrollY default value is "0"
+
+ final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
+ final int scrollRange = Math.max(0,
+ childHeight - (b - t - mPaddingBottom - mPaddingTop));
+
// Don't forget to clamp
if (mScrollY > scrollRange) {
mScrollY = scrollRange;
diff --git a/docs/html/guide/appendix/media-formats.jd b/docs/html/guide/appendix/media-formats.jd
index 069a603..9070968 100644
--- a/docs/html/guide/appendix/media-formats.jd
+++ b/docs/html/guide/appendix/media-formats.jd
@@ -259,7 +259,7 @@ rates for raw PCM recordings at 8000, 16000 and 44100 Hz.</td>
<tr>
<td>VP8</td>
-<td>&nbsp;</td>
+<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 4.3+)</small></td>
<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 2.3.3+)</small></td>
<td>Streamable only in Android 4.0 and above</td>
<td>
@@ -272,9 +272,17 @@ rates for raw PCM recordings at 8000, 16000 and 44100 Hz.</td>
<h2 id="recommendations">Video Encoding Recommendations</h2>
-<p>Table 2, below, lists examples of video encoding profiles and parameters that the Android media framework supports for playback. In addition to these encoding parameter recommendations, a device's available <em>video recording</em> profiles can be used as a proxy for media playback capabilities. These profiles can be inspected using the {@link android.media.CamcorderProfile CamcorderProfile} class, which is available since API level 8.</p>
+<p>Table 2, below, lists examples of video encoding profiles and parameters that the Android
+media framework supports for playback in the H.264 Baseline Profile codec. While
+table 3 lists examples that the framework supports for playback in the VP8 media codec.</p>
+
+<p>In addition to these encoding parameter recommendations,
+a device's available <em>video recording</em> profiles can be used as a proxy for media playback
+capabilities. These profiles can be inspected using the {@link android.media.CamcorderProfile
+CamcorderProfile} class, which is available since API level 8.</p>
-<p class="table-caption" id="encoding-recommendations-table"><strong>Table 2.</strong> Examples of supported video encoding parameters.</p>
+<p class="table-caption" id="encoding-recommendations-table"><strong>Table 2.</strong>
+Examples of supported video encoding parameters for the H.264 Baseline Profile codec.</p>
<table>
<thead>
@@ -282,17 +290,11 @@ rates for raw PCM recordings at 8000, 16000 and 44100 Hz.</td>
<th>&nbsp;</th>
<th><acronym title="Standard definition">SD</a> (Low quality)</th>
<th><acronym title="Standard definition">SD</a> (High quality)</th>
- <th><acronym title="High definition">HD</a> (Not available on all devices)</th>
+ <th><acronym title="High definition">HD 720p</a> (N/A on all devices)</th>
</tr>
</thead>
<tbody>
<tr>
- <th>Video codec</th>
- <td>H.264 Baseline Profile</td>
- <td>H.264 Baseline Profile</td>
- <td>H.264 Baseline Profile</td>
- </tr>
- <tr>
<th>Video resolution</th>
<td>176 x 144 px</td>
<td>480 x 360 px</td>
@@ -331,6 +333,49 @@ rates for raw PCM recordings at 8000, 16000 and 44100 Hz.</td>
</tbody>
</table>
+
+
+<p class="table-caption" id="encoding-recommendations-table-vp8"><strong>Table 3.</strong>
+Examples of supported video encoding parameters for the VP8 codec.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th>&nbsp;</th>
+ <th><acronym title="Standard definition">SD</a> (Low quality)</th>
+ <th><acronym title="Standard definition">SD</a> (High quality)</th>
+ <th><acronym title="High definition">HD 720p</a> (N/A on all devices)</th>
+ <th><acronym title="High definition">HD 1080p</a> (N/A on all devices)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>Video resolution</th>
+ <td>320 x 180 px</td>
+ <td>640 x 360 px</td>
+ <td>1280 x 720 px</td>
+ <td>1920 x 1080 px</td>
+ </tr>
+ <tr>
+ <th>Video frame rate</th>
+ <td>30 fps</td>
+ <td>30 fps</td>
+ <td>30 fps</td>
+ <td>30 fps</td>
+ </tr>
+ <tr>
+ <th>Video bitrate</th>
+ <td>800 Kbps</td>
+ <td>2 Mbps</td>
+ <td>4 Mbps</td>
+ <td>10 Mbps</td>
+ </tr>
+ </tbody>
+</table>
+
+
+
+
<p style="margin-top: 2em">For video content that is streamed over HTTP or RTSP, there are additional requirements:</p>
<ul>
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
index c9c54b2..d54b675 100644
--- a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
+++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
@@ -48,7 +48,10 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic {
*/
public static ScriptIntrinsicConvolve3x3 create(RenderScript rs, Element e) {
float f[] = { 0, 0, 0, 0, 1, 0, 0, 0, 0};
- if (!e.isCompatible(Element.U8_4(rs))) {
+ if (!e.isCompatible(Element.U8(rs)) &&
+ !e.isCompatible(Element.U8_4(rs)) &&
+ !e.isCompatible(Element.F32(rs)) &&
+ !e.isCompatible(Element.F32_4(rs))) {
throw new RSIllegalArgumentException("Unsuported element type.");
}
int id = rs.nScriptIntrinsicCreate(1, e.getID(rs));
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
index c6e1e39..da4d5a2 100644
--- a/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
+++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -48,6 +48,13 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic {
* @return ScriptIntrinsicConvolve5x5
*/
public static ScriptIntrinsicConvolve5x5 create(RenderScript rs, Element e) {
+ if (!e.isCompatible(Element.U8(rs)) &&
+ !e.isCompatible(Element.U8_4(rs)) &&
+ !e.isCompatible(Element.F32(rs)) &&
+ !e.isCompatible(Element.F32_4(rs))) {
+ throw new RSIllegalArgumentException("Unsuported element type.");
+ }
+
int id = rs.nScriptIntrinsicCreate(4, e.getID(rs));
return new ScriptIntrinsicConvolve5x5(id, rs);
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java b/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java
new file mode 100644
index 0000000..f143326
--- /dev/null
+++ b/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.renderscript;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+/**
+ * Intrinsic Histogram filter.
+ *
+ *
+ **/
+public final class ScriptIntrinsicHistogram extends ScriptIntrinsic {
+ private Allocation mOut;
+
+ private ScriptIntrinsicHistogram(int id, RenderScript rs) {
+ super(id, rs);
+ }
+
+ /**
+ * Create an intrinsic for calculating the histogram of an uchar
+ * or uchar4 image.
+ *
+ * Supported elements types are
+ * {@link Element#U8_4}, {@link Element#U8_3},
+ * {@link Element#U8_2}, {@link Element#U8}
+ *
+ * @param rs The RenderScript context
+ * @param e Element type for inputs
+ *
+ * @return ScriptIntrinsicHistogram
+ */
+ public static ScriptIntrinsicHistogram create(RenderScript rs, Element e) {
+ if ((!e.isCompatible(Element.U8_4(rs))) &&
+ (!e.isCompatible(Element.U8_3(rs))) &&
+ (!e.isCompatible(Element.U8_2(rs))) &&
+ (!e.isCompatible(Element.U8(rs)))) {
+ throw new RSIllegalArgumentException("Unsuported element type.");
+ }
+ int id = rs.nScriptIntrinsicCreate(9, e.getID(rs));
+ ScriptIntrinsicHistogram sib = new ScriptIntrinsicHistogram(id, rs);
+ return sib;
+ }
+
+ /**
+ * Process an input buffer and place the histogram into the
+ * output allocation. The output allocation may be a narrower
+ * vector size than the input. In this case the vector size of
+ * the output is used to determine how many of the input
+ * channels are used in the computation. This is useful if you
+ * have an RGBA input buffer but only want the histogram for
+ * RGB.
+ *
+ * 1D and 2D input allocations are supported.
+ *
+ * @param ain The input image
+ */
+ public void forEach(Allocation ain) {
+ if (ain.getType().getElement().getVectorSize() <
+ mOut.getType().getElement().getVectorSize()) {
+
+ throw new RSIllegalArgumentException(
+ "Input vector size must be >= output vector size.");
+ }
+ if (ain.getType().getElement().isCompatible(Element.U8(mRS)) &&
+ ain.getType().getElement().isCompatible(Element.U8_4(mRS))) {
+ throw new RSIllegalArgumentException("Output type must be U32 or I32.");
+ }
+
+ forEach(0, ain, null, null);
+ }
+
+ /**
+ * Set the coefficients used for the RGBA to Luminocity
+ * calculation. The default is {0.299f, 0.587f, 0.114f, 0.f}.
+ *
+ * Coefficients must be >= 0 and sum to 1.0 or less.
+ *
+ * @param r Red coefficient
+ * @param g Green coefficient
+ * @param b Blue coefficient
+ * @param a Alpha coefficient
+ */
+ public void setDotCoefficients(float r, float g, float b, float a) {
+ if ((r < 0.f) || (g < 0.f) || (b < 0.f) || (a < 0.f)) {
+ throw new RSIllegalArgumentException("Coefficient may not be negative.");
+ }
+ if ((r + g + b + a) > 1.f) {
+ throw new RSIllegalArgumentException("Sum of coefficients must be 1.0 or less.");
+ }
+
+ FieldPacker fp = new FieldPacker(16);
+ fp.addF32(r);
+ fp.addF32(g);
+ fp.addF32(b);
+ fp.addF32(a);
+ setVar(0, fp);
+ }
+
+ /**
+ * Set the output of the histogram. 32 bit integer types are
+ * supported.
+ *
+ * @param aout The output allocation
+ */
+ public void setOutput(Allocation aout) {
+ mOut = aout;
+ if (mOut.getType().getElement() != Element.U32(mRS) &&
+ mOut.getType().getElement() != Element.U32_2(mRS) &&
+ mOut.getType().getElement() != Element.U32_3(mRS) &&
+ mOut.getType().getElement() != Element.U32_4(mRS) &&
+ mOut.getType().getElement() != Element.I32(mRS) &&
+ mOut.getType().getElement() != Element.I32_2(mRS) &&
+ mOut.getType().getElement() != Element.I32_3(mRS) &&
+ mOut.getType().getElement() != Element.I32_4(mRS)) {
+
+ throw new RSIllegalArgumentException("Output type must be U32 or I32.");
+ }
+ if ((mOut.getType().getX() != 256) ||
+ (mOut.getType().getY() != 0) ||
+ mOut.getType().hasMipmaps() ||
+ (mOut.getType().getYuv() != 0)) {
+
+ throw new RSIllegalArgumentException("Output must be 1D, 256 elements.");
+ }
+ setVar(1, aout);
+ }
+
+ /**
+ * Process an input buffer and place the histogram into the
+ * output allocation. The dot product of the input channel and
+ * the coefficients from 'setDotCoefficients' are used to
+ * calculate the output values.
+ *
+ * 1D and 2D input allocations are supported.
+ *
+ * @param ain The input image
+ */
+ public void forEach_dot(Allocation ain) {
+ if (mOut.getType().getElement().getVectorSize() != 1) {
+ throw new RSIllegalArgumentException("Output vector size must be one.");
+ }
+ if (ain.getType().getElement().isCompatible(Element.U8(mRS)) &&
+ ain.getType().getElement().isCompatible(Element.U8_4(mRS))) {
+ throw new RSIllegalArgumentException("Output type must be U32 or I32.");
+ }
+
+ forEach(1, ain, null, null);
+ }
+
+
+
+ /**
+ * Get a KernelID for this intrinsic kernel.
+ *
+ * @return Script.KernelID The KernelID object.
+ */
+ public Script.KernelID getKernelID_seperate() {
+ return createKernelID(0, 3, null, null);
+ }
+
+ /**
+ * Get a FieldID for the input field of this intrinsic.
+ *
+ * @return Script.FieldID The FieldID object.
+ */
+ public Script.FieldID getFieldID_Input() {
+ return createFieldID(1, null);
+ }
+}
+
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml
index 7a40a9e..7d77c48 100644
--- a/packages/Keyguard/AndroidManifest.xml
+++ b/packages/Keyguard/AndroidManifest.xml
@@ -39,7 +39,7 @@
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<application android:label="@string/app_name"
- android:process="com.android.systemui.keyguard"
+ android:process="com.android.systemui"
android:persistent="true" >
<service android:name=".KeyguardService"
diff --git a/preloaded-classes b/preloaded-classes
index 2aa610a..45d27ee 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1220,7 +1220,6 @@ android.webkit.HTML5Audio
android.webkit.HTML5VideoViewProxy
android.webkit.JWebCoreJavaBridge
android.webkit.JavascriptInterface
-android.webkit.JniUtil
android.webkit.L10nUtils
android.webkit.MockGeolocation
android.webkit.OverScrollGlow
@@ -1269,7 +1268,6 @@ android.webkit.WebViewClassic$TitleBarDelegate
android.webkit.WebViewClassic$TrustStorageListener
android.webkit.WebViewClassic$ViewSizeData
android.webkit.WebViewClient
-android.webkit.WebViewCore
android.webkit.WebViewCore$AutoFillData
android.webkit.WebViewCore$DrawData
android.webkit.WebViewCore$EventHub
@@ -1283,6 +1281,7 @@ android.webkit.WebViewDatabase
android.webkit.WebViewDatabaseClassic
android.webkit.WebViewDatabaseClassic$1
android.webkit.WebViewFactory
+android.webkit.WebViewFactory$Preloader
android.webkit.WebViewFactoryProvider
android.webkit.WebViewFactoryProvider$Statics
android.webkit.WebViewInputDispatcher
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index c558fbd..cd718a2 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -1511,6 +1511,7 @@ public final class ActiveServices {
mPendingServices.remove(i);
i--;
+ proc.addPackage(sr.appInfo.packageName, mAm.mProcessTracker);
realStartServiceLocked(sr, proc);
didSomething = true;
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 62520d5..bdfa447 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -198,32 +198,32 @@ public final class ActivityManagerService extends ActivityManagerNative
static final String TAG_MU = "ActivityManagerServiceMU";
static final boolean DEBUG = false;
static final boolean localLOGV = DEBUG;
- static final boolean DEBUG_SWITCH = localLOGV || false;
- static final boolean DEBUG_TASKS = localLOGV || false;
- static final boolean DEBUG_THUMBNAILS = localLOGV || false;
- static final boolean DEBUG_PAUSE = localLOGV || false;
- static final boolean DEBUG_OOM_ADJ = localLOGV || false;
- static final boolean DEBUG_TRANSITION = localLOGV || false;
+ static final boolean DEBUG_BACKUP = localLOGV || false;
static final boolean DEBUG_BROADCAST = localLOGV || false;
- static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false;
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
- static final boolean DEBUG_SERVICE = localLOGV || false;
- static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
- static final boolean DEBUG_VISBILITY = localLOGV || false;
- static final boolean DEBUG_PROCESSES = localLOGV || false;
- static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false;
+ static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false;
static final boolean DEBUG_CLEANUP = localLOGV || false;
- static final boolean DEBUG_PROVIDER = localLOGV || false;
- static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
- static final boolean DEBUG_USER_LEAVING = localLOGV || false;
- static final boolean DEBUG_RESULTS = localLOGV || false;
- static final boolean DEBUG_BACKUP = localLOGV || false;
static final boolean DEBUG_CONFIGURATION = localLOGV || false;
+ static final boolean DEBUG_IMMERSIVE = localLOGV || false;
+ static final boolean DEBUG_MU = localLOGV || false;
+ static final boolean DEBUG_OOM_ADJ = localLOGV || false;
+ static final boolean DEBUG_PAUSE = localLOGV || false;
static final boolean DEBUG_POWER = localLOGV || false;
static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
- static final boolean DEBUG_MU = localLOGV || false;
- static final boolean DEBUG_IMMERSIVE = localLOGV || false;
- static final boolean DEBUG_STACK = localLOGV || true;
+ static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false;
+ static final boolean DEBUG_PROCESSES = localLOGV || false;
+ static final boolean DEBUG_PROVIDER = localLOGV || false;
+ static final boolean DEBUG_RESULTS = localLOGV || false;
+ static final boolean DEBUG_SERVICE = localLOGV || false;
+ static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
+ static final boolean DEBUG_STACK = localLOGV || false;
+ static final boolean DEBUG_SWITCH = localLOGV || false;
+ static final boolean DEBUG_TASKS = localLOGV || false;
+ static final boolean DEBUG_THUMBNAILS = localLOGV || false;
+ static final boolean DEBUG_TRANSITION = localLOGV || false;
+ static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
+ static final boolean DEBUG_USER_LEAVING = localLOGV || false;
+ static final boolean DEBUG_VISBILITY = localLOGV || false;
static final boolean VALIDATE_TOKENS = true;
static final boolean SHOW_ACTIVITY_START_TIME = true;
@@ -6281,6 +6281,7 @@ public final class ActivityManagerService extends ActivityManagerNative
enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
"moveTaskToFront()");
+ if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving task=" + task);
synchronized(this) {
if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
Binder.getCallingUid(), "Task to front")) {
@@ -6305,6 +6306,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized(this) {
TaskRecord tr = recentTaskForIdLocked(taskId);
if (tr != null) {
+ if (DEBUG_STACK) Slog.d(TAG, "moveTaskToBack: moving task=" + tr);
ActivityStack stack = tr.stack;
if (stack.mResumedActivity != null && stack.mResumedActivity.task == tr) {
if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
@@ -6383,6 +6385,8 @@ public final class ActivityManagerService extends ActivityManagerNative
new RuntimeException("here").fillInStackTrace());
}
synchronized (this) {
+ if (DEBUG_STACK) Slog.d(TAG, "moveTaskToStack: moving task=" + taskId + " to stackId="
+ + stackId + " toTop=" + toTop);
mStackSupervisor.moveTaskToStack(taskId, stackId, toTop);
}
}
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 8224c88..a4fd7ad 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1186,7 +1186,8 @@ public final class ActivityStackSupervisor {
}
ActivityStack getCorrectStack(ActivityRecord r) {
- if (r.isApplicationActivity() || (r.task != null && r.task.isApplicationTask())) {
+ final TaskRecord task = r.task;
+ if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) {
int stackNdx;
for (stackNdx = mStacks.size() - 1; stackNdx > 0; --stackNdx) {
if (mStacks.get(stackNdx).mCurrentUser == mCurrentUser) {
@@ -1199,6 +1200,9 @@ public final class ActivityStackSupervisor {
StackBox.TASK_STACK_GOES_OVER, 1.0f);
mFocusedStack = getStack(stackId);
}
+ if (task != null) {
+ mFocusedStack = task.stack;
+ }
return mFocusedStack;
}
return mHomeStack;
@@ -1649,6 +1653,7 @@ public final class ActivityStackSupervisor {
ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
setLaunchHomeTaskNextFlag(sourceRecord, r, targetStack);
targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
+ mService.setFocusedActivityLocked(r);
return ActivityManager.START_SUCCESS;
}
@@ -1867,6 +1872,8 @@ public final class ActivityStackSupervisor {
void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
if (mStacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) {
+ if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack=" +
+ mStacks.get(stackNdx));
return;
}
}
@@ -2053,7 +2060,6 @@ public final class ActivityStackSupervisor {
final ActivityStack stack = r.task.stack;
if (isFrontStack(stack)) {
mService.updateUsageStats(r, true);
- mService.setFocusedActivityLocked(r);
}
if (allResumedActivitiesComplete()) {
ensureActivitiesVisibleLocked(null, 0);
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index 247d8a0..11f8d6a 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -61,6 +61,18 @@ final class DisplayDeviceInfo {
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1 << 3;
/**
+ * Flag: Indicates that the display device is owned by a particular application
+ * and that no other application should be able to interact with it.
+ */
+ public static final int FLAG_PRIVATE = 1 << 4;
+
+ /**
+ * Flag: Indicates that the display device is not blanked automatically by
+ * the power manager.
+ */
+ public static final int FLAG_NEVER_BLANK = 1 << 5;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -150,6 +162,23 @@ final class DisplayDeviceInfo {
*/
public String address;
+ /**
+ * The UID of the application that owns this display, or zero if it is owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public int ownerUid;
+
+ /**
+ * The package name of the application that owns this display, or null if it is
+ * owned by the system.
+ * <p>
+ * If the display is private, then only the owner can use it.
+ * </p>
+ */
+ public String ownerPackageName;
+
public void setAssumedDensityForExternalDisplay(int width, int height) {
densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
// Technically, these values should be smaller than the apparent density
@@ -176,7 +205,9 @@ final class DisplayDeviceInfo {
&& touch == other.touch
&& rotation == other.rotation
&& type == other.type
- && Objects.equal(address, other.address);
+ && Objects.equal(address, other.address)
+ && ownerUid == other.ownerUid
+ && Objects.equal(ownerPackageName, other.ownerPackageName);
}
@Override
@@ -197,19 +228,32 @@ final class DisplayDeviceInfo {
rotation = other.rotation;
type = other.type;
address = other.address;
+ ownerUid = other.ownerUid;
+ ownerPackageName = other.ownerPackageName;
}
// For debugging purposes
@Override
public String toString() {
- return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", "
- + refreshRate + " fps, "
- + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
- + ", touch " + touchToString(touch) + flagsToString(flags)
- + ", rotation " + rotation
- + ", type " + Display.typeToString(type)
- + ", address " + address
- + "}";
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayDeviceInfo{\"");
+ sb.append(name).append("\": ").append(width).append(" x ").append(height);
+ sb.append(", ").append(refreshRate).append(" fps, ");
+ sb.append("density ").append(densityDpi);
+ sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
+ sb.append(", touch ").append(touchToString(touch));
+ sb.append(", rotation ").append(rotation);
+ sb.append(", type ").append(Display.typeToString(type));
+ if (address != null) {
+ sb.append(", address ").append(address);
+ }
+ if (ownerUid != 0 || ownerPackageName != null) {
+ sb.append(", owner ").append(ownerPackageName);
+ sb.append(" (uid ").append(ownerUid).append(")");
+ }
+ sb.append(flagsToString(flags));
+ sb.append("}");
+ return sb.toString();
}
private static String touchToString(int touch) {
@@ -239,6 +283,12 @@ final class DisplayDeviceInfo {
if ((flags & FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
msg.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS");
}
+ if ((flags & FLAG_PRIVATE) != 0) {
+ msg.append(", FLAG_PRIVATE");
+ }
+ if ((flags & FLAG_NEVER_BLANK) != 0) {
+ msg.append(", FLAG_NEVER_BLANK");
+ }
return msg.toString();
}
}
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index ca85e42..c339c26 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -33,15 +33,19 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
+
import com.android.server.UiThread;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -171,6 +175,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
// The Wifi display adapter, or null if not registered.
private WifiDisplayAdapter mWifiDisplayAdapter;
+ // The virtual display adapter, or null if not registered.
+ private VirtualDisplayAdapter mVirtualDisplayAdapter;
+
// Viewports of the default display and the display that should receive touch
// input from an external source. Used by the input system.
private final DisplayViewport mDefaultViewport = new DisplayViewport();
@@ -363,13 +370,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
synchronized (mSyncRoot) {
if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_BLANKED) {
mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_BLANKED;
-
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- DisplayDevice device = mDisplayDevices.get(i);
- device.blankLocked();
- }
-
+ updateAllDisplayBlankingLocked();
scheduleTraversalLocked(false);
}
}
@@ -382,13 +383,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
synchronized (mSyncRoot) {
if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_UNBLANKED) {
mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_UNBLANKED;
-
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- DisplayDevice device = mDisplayDevices.get(i);
- device.unblankLocked();
- }
-
+ updateAllDisplayBlankingLocked();
scheduleTraversalLocked(false);
}
}
@@ -403,12 +398,21 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
*/
@Override // Binder call
public DisplayInfo getDisplayInfo(int displayId) {
- synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
- if (display != null) {
- return display.getDisplayInfoLocked();
+ final int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null) {
+ DisplayInfo info = display.getDisplayInfoLocked();
+ if (info.hasAccess(callingUid)) {
+ return info;
+ }
+ }
+ return null;
}
- return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -417,13 +421,27 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
*/
@Override // Binder call
public int[] getDisplayIds() {
- synchronized (mSyncRoot) {
- final int count = mLogicalDisplays.size();
- int[] displayIds = new int[count];
- for (int i = 0; i < count; i++) {
- displayIds[i] = mLogicalDisplays.keyAt(i);
+ final int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ final int count = mLogicalDisplays.size();
+ int[] displayIds = new int[count];
+ int n = 0;
+ for (int i = 0; i < count; i++) {
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ DisplayInfo info = display.getDisplayInfoLocked();
+ if (info.hasAccess(callingUid)) {
+ displayIds[n++] = mLogicalDisplays.keyAt(i);
+ }
+ }
+ if (n != count) {
+ displayIds = Arrays.copyOfRange(displayIds, 0, n);
+ }
+ return displayIds;
}
- return displayIds;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -570,6 +588,95 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
== PackageManager.PERMISSION_GRANTED;
}
+ @Override // Binder call
+ public int createPrivateVirtualDisplay(IBinder appToken, String packageName,
+ String name, int width, int height, int densityDpi, Surface surface) {
+ final int callingUid = Binder.getCallingUid();
+ if (!validatePackageName(callingUid, packageName)) {
+ throw new SecurityException("packageName must match the calling uid");
+ }
+ if (appToken == null) {
+ throw new IllegalArgumentException("appToken must not be null");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must be non-null and non-empty");
+ }
+ if (width <= 0 || height <= 0 || densityDpi <= 0) {
+ throw new IllegalArgumentException("width, height, and densityDpi must be "
+ + "greater than 0");
+ }
+ if (surface == null) {
+ throw new IllegalArgumentException("surface must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ Slog.w(TAG, "Rejecting request to create private virtual display "
+ + "because the virtual display adapter is not available.");
+ return -1;
+ }
+
+ DisplayDevice device = mVirtualDisplayAdapter.createPrivateVirtualDisplayLocked(
+ appToken, callingUid, packageName, name, width, height, densityDpi,
+ surface);
+ if (device == null) {
+ return -1;
+ }
+
+ handleDisplayDeviceAddedLocked(device);
+ LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+ if (display != null) {
+ return display.getDisplayIdLocked();
+ }
+
+ // Something weird happened and the logical display was not created.
+ Slog.w(TAG, "Rejecting request to create private virtual display "
+ + "because the logical display was not created.");
+ mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ handleDisplayDeviceRemovedLocked(device);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return -1;
+ }
+
+ @Override // Binder call
+ public void releaseVirtualDisplay(IBinder appToken) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ return;
+ }
+
+ DisplayDevice device =
+ mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ if (device != null) {
+ handleDisplayDeviceRemovedLocked(device);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean validatePackageName(int uid, String packageName) {
+ if (packageName != null) {
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packageNames != null) {
+ for (String n : packageNames) {
+ if (n.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
private void registerDefaultDisplayAdapter() {
// Register default display adapter.
synchronized (mSyncRoot) {
@@ -588,6 +695,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
registerOverlayDisplayAdapterLocked();
registerWifiDisplayAdapterLocked();
+ registerVirtualDisplayAdapterLocked();
}
}
}
@@ -608,6 +716,12 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
}
+ private void registerVirtualDisplayAdapterLocked() {
+ mVirtualDisplayAdapter = new VirtualDisplayAdapter(
+ mSyncRoot, mContext, mHandler, mDisplayAdapterListener);
+ registerDisplayAdapterLocked(mVirtualDisplayAdapter);
+ }
+
private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() {
// In safe mode, we disable non-essential display adapters to give the user
// an opportunity to fix broken settings or other problems that might affect
@@ -625,29 +739,23 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
private void handleDisplayDeviceAdded(DisplayDevice device) {
synchronized (mSyncRoot) {
- if (mDisplayDevices.contains(device)) {
- Slog.w(TAG, "Attempted to add already added display device: "
- + device.getDisplayDeviceInfoLocked());
- return;
- }
+ handleDisplayDeviceAddedLocked(device);
+ }
+ }
- Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
+ private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
+ if (mDisplayDevices.contains(device)) {
+ Slog.w(TAG, "Attempted to add already added display device: "
+ + device.getDisplayDeviceInfoLocked());
+ return;
+ }
- mDisplayDevices.add(device);
- addLogicalDisplayLocked(device);
- scheduleTraversalLocked(false);
+ Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
- // Blank or unblank the display immediately to match the state requested
- // by the power manager (if known).
- switch (mAllDisplayBlankStateFromPowerManager) {
- case DISPLAY_BLANK_STATE_BLANKED:
- device.blankLocked();
- break;
- case DISPLAY_BLANK_STATE_UNBLANKED:
- device.unblankLocked();
- break;
- }
- }
+ mDisplayDevices.add(device);
+ addLogicalDisplayLocked(device);
+ updateDisplayBlankingLocked(device);
+ scheduleTraversalLocked(false);
}
private void handleDisplayDeviceChanged(DisplayDevice device) {
@@ -669,17 +777,44 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
private void handleDisplayDeviceRemoved(DisplayDevice device) {
synchronized (mSyncRoot) {
- if (!mDisplayDevices.remove(device)) {
- Slog.w(TAG, "Attempted to remove non-existent display device: "
- + device.getDisplayDeviceInfoLocked());
- return;
- }
+ handleDisplayDeviceRemovedLocked(device);
+ }
+ }
+ private void handleDisplayDeviceRemovedLocked(DisplayDevice device) {
+ if (!mDisplayDevices.remove(device)) {
+ Slog.w(TAG, "Attempted to remove non-existent display device: "
+ + device.getDisplayDeviceInfoLocked());
+ return;
+ }
- Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
+ Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
- mRemovedDisplayDevices.add(device);
- updateLogicalDisplaysLocked();
- scheduleTraversalLocked(false);
+ mRemovedDisplayDevices.add(device);
+ updateLogicalDisplaysLocked();
+ scheduleTraversalLocked(false);
+ }
+
+ private void updateAllDisplayBlankingLocked() {
+ final int count = mDisplayDevices.size();
+ for (int i = 0; i < count; i++) {
+ DisplayDevice device = mDisplayDevices.get(i);
+ updateDisplayBlankingLocked(device);
+ }
+ }
+
+ private void updateDisplayBlankingLocked(DisplayDevice device) {
+ // Blank or unblank the display immediately to match the state requested
+ // by the power manager (if known).
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+ switch (mAllDisplayBlankStateFromPowerManager) {
+ case DISPLAY_BLANK_STATE_BLANKED:
+ device.blankLocked();
+ break;
+ case DISPLAY_BLANK_STATE_UNBLANKED:
+ device.unblankLocked();
+ break;
+ }
}
}
@@ -812,13 +947,21 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
private void configureDisplayInTransactionLocked(DisplayDevice device) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ boolean isPrivate = (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0;
+
// Find the logical display that the display device is showing.
+ // Private displays never mirror other displays.
LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
- if (display != null && !display.hasContentLocked()) {
- display = null;
- }
- if (display == null) {
- display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+ if (!isPrivate) {
+ if (display != null && !display.hasContentLocked()) {
+ // If the display does not have any content of its own, then
+ // automatically mirror the default logical display contents.
+ display = null;
+ }
+ if (display == null) {
+ display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+ }
}
// Apply the logical display configuration to the display device.
@@ -828,11 +971,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
+ device.getDisplayDeviceInfoLocked());
return;
}
- boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED);
+ boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED)
+ && (info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0;
display.configureDisplayInTransactionLocked(device, isBlanked);
// Update the viewports if needed.
- DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if (!mDefaultViewport.valid
&& (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
setViewportLocked(mDefaultViewport, display, device);
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index 424ec36..775ebb2 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -202,6 +202,9 @@ final class LogicalDisplay {
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SECURE) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_SECURE;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0) {
+ mBaseDisplayInfo.flags |= Display.FLAG_PRIVATE;
+ }
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.name = deviceInfo.name;
@@ -218,6 +221,8 @@ final class LogicalDisplay {
mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height;
mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width;
mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height;
+ mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid;
+ mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName;
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo = null;
diff --git a/services/java/com/android/server/display/VirtualDisplayAdapter.java b/services/java/com/android/server/display/VirtualDisplayAdapter.java
new file mode 100644
index 0000000..634fba7
--- /dev/null
+++ b/services/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -0,0 +1,161 @@
+/*
+ * 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.server.display;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+/**
+ * A display adapter that provides virtual displays on behalf of applications.
+ * <p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class VirtualDisplayAdapter extends DisplayAdapter {
+ static final String TAG = "VirtualDisplayAdapter";
+ static final boolean DEBUG = false;
+
+ private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
+ new ArrayMap<IBinder, VirtualDisplayDevice>();
+
+ // Called with SyncRoot lock held.
+ public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+ Context context, Handler handler, Listener listener) {
+ super(syncRoot, context, handler, listener, TAG);
+ }
+
+ public DisplayDevice createPrivateVirtualDisplayLocked(IBinder appToken,
+ int ownerUid, String ownerPackageName,
+ String name, int width, int height, int densityDpi, Surface surface) {
+ IBinder displayToken = SurfaceControl.createDisplay(name, false /*secure*/);
+ VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
+ ownerUid, ownerPackageName, name, width, height, densityDpi, surface);
+
+ try {
+ appToken.linkToDeath(device, 0);
+ } catch (RemoteException ex) {
+ device.releaseLocked();
+ return null;
+ }
+
+ mVirtualDisplayDevices.put(appToken, device);
+
+ // Return the display device without actually sending the event indicating
+ // that it was added. The caller will handle it.
+ return device;
+ }
+
+ public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+ if (device != null) {
+ appToken.unlinkToDeath(device, 0);
+ }
+
+ // Return the display device that was removed without actually sending the
+ // event indicating that it was removed. The caller will handle it.
+ return device;
+ }
+
+ private void handleBinderDiedLocked(IBinder appToken) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+ if (device != null) {
+ Slog.i(TAG, "Virtual display device released because application token died: "
+ + device.mOwnerPackageName);
+ sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+ }
+ }
+
+ private final class VirtualDisplayDevice extends DisplayDevice
+ implements DeathRecipient {
+ private final IBinder mAppToken;
+ private final int mOwnerUid;
+ final String mOwnerPackageName;
+ private final String mName;
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+
+ private boolean mReleased;
+ private Surface mSurface;
+ private DisplayDeviceInfo mInfo;
+
+ public VirtualDisplayDevice(IBinder displayToken,
+ IBinder appToken, int ownerUid, String ownerPackageName,
+ String name, int width, int height, int densityDpi, Surface surface) {
+ super(VirtualDisplayAdapter.this, displayToken);
+ mAppToken = appToken;
+ mOwnerUid = ownerUid;
+ mOwnerPackageName = ownerPackageName;
+ mName = name;
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+ mSurface = surface;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (getSyncRoot()) {
+ if (!mReleased) {
+ handleBinderDiedLocked(mAppToken);
+ }
+ }
+ }
+
+ public void releaseLocked() {
+ mReleased = true;
+ sendTraversalRequestLocked();
+ }
+
+ @Override
+ public void performTraversalInTransactionLocked() {
+ if (mReleased && mSurface != null) {
+ mSurface.destroy();
+ mSurface = null;
+ }
+ setSurfaceInTransactionLocked(mSurface);
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ if (mInfo == null) {
+ mInfo = new DisplayDeviceInfo();
+ mInfo.name = mName;
+ mInfo.width = mWidth;
+ mInfo.height = mHeight;
+ mInfo.refreshRate = 60;
+ mInfo.densityDpi = mDensityDpi;
+ mInfo.xDpi = mDensityDpi;
+ mInfo.yDpi = mDensityDpi;
+ mInfo.flags = DisplayDeviceInfo.FLAG_PRIVATE | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+ mInfo.type = Display.TYPE_VIRTUAL;
+ mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+ mInfo.ownerUid = mOwnerUid;
+ mInfo.ownerPackageName = mOwnerPackageName;
+ }
+ return mInfo;
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 82e8c7f..feac370 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -140,6 +140,13 @@ class DisplayContent {
return mDisplayInfo;
}
+ /**
+ * Returns true if the specified UID has access to this display.
+ */
+ public boolean hasAccess(int uid) {
+ return mDisplay.hasAccess(uid);
+ }
+
boolean homeOnTop() {
return mStackBoxes.get(0).mStack != mHomeStack;
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index faeb37c..fd06535 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2105,6 +2105,13 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent == null) {
+ Slog.w(TAG, "Attempted to add window to a display that does not exist: "
+ + displayId + ". Aborting.");
+ return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+ }
+ if (!displayContent.hasAccess(session.mUid)) {
+ Slog.w(TAG, "Attempted to add window to a display for which the application "
+ + "does not have access: " + displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
@@ -3232,8 +3239,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (taskNdx >= 0 || t >= 0) {
Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks);
- Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager="
- + displayContent.getTasks());
+ Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + localTasks);
Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4));
}
}
@@ -4838,6 +4844,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId);
return;
}
final TaskStack stack = task.mStack;
@@ -7521,7 +7528,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
synchronized(displayContent.mDisplaySizeLock) {
size.x = displayContent.mInitialDisplayWidth;
size.y = displayContent.mInitialDisplayHeight;
@@ -7534,7 +7541,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void getBaseDisplaySize(int displayId, Point size) {
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
synchronized(displayContent.mDisplaySizeLock) {
size.x = displayContent.mBaseDisplayWidth;
size.y = displayContent.mBaseDisplayHeight;
@@ -7649,7 +7656,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int getInitialDisplayDensity(int displayId) {
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
synchronized(displayContent.mDisplaySizeLock) {
return displayContent.mInitialDisplayDensity;
}
@@ -7662,7 +7669,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int getBaseDisplayDensity(int displayId) {
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
+ if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
synchronized(displayContent.mDisplaySizeLock) {
return displayContent.mBaseDisplayDensity;
}
diff --git a/tests/AccessoryDisplay/Android.mk b/tests/AccessoryDisplay/Android.mk
new file mode 100644
index 0000000..85cb309
--- /dev/null
+++ b/tests/AccessoryDisplay/Android.mk
@@ -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))
diff --git a/tests/AccessoryDisplay/README b/tests/AccessoryDisplay/README
new file mode 100644
index 0000000..5ce558c
--- /dev/null
+++ b/tests/AccessoryDisplay/README
@@ -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.
+
diff --git a/tests/AccessoryDisplay/common/Android.mk b/tests/AccessoryDisplay/common/Android.mk
new file mode 100644
index 0000000..2d4de15
--- /dev/null
+++ b/tests/AccessoryDisplay/common/Android.mk
@@ -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)
diff --git a/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/BufferPool.java b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/BufferPool.java
new file mode 100644
index 0000000..a6bb5c1
--- /dev/null
+++ b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/BufferPool.java
@@ -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;
+ }
+}
diff --git a/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Logger.java b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Logger.java
new file mode 100644
index 0000000..e0b7e82
--- /dev/null
+++ b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Logger.java
@@ -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);
+ }
+}
diff --git a/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Protocol.java b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Protocol.java
new file mode 100644
index 0000000..46fee32
--- /dev/null
+++ b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Protocol.java
@@ -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;
+ }
+}
diff --git a/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Service.java b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Service.java
new file mode 100644
index 0000000..70b3806
--- /dev/null
+++ b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Service.java
@@ -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) {
+ }
+}
diff --git a/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Transport.java b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Transport.java
new file mode 100644
index 0000000..84897d3
--- /dev/null
+++ b/tests/AccessoryDisplay/common/src/com/android/accessorydisplay/common/Transport.java
@@ -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;
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/sink/Android.mk b/tests/AccessoryDisplay/sink/Android.mk
new file mode 100644
index 0000000..772ce0c
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the application.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := 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)
diff --git a/tests/AccessoryDisplay/sink/AndroidManifest.xml b/tests/AccessoryDisplay/sink/AndroidManifest.xml
new file mode 100644
index 0000000..72d498f
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.accessorydisplay.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>
diff --git a/tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png b/tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.png b/tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/res/drawable-mdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/sink/res/layout/sink_activity.xml b/tests/AccessoryDisplay/sink/res/layout/sink_activity.xml
new file mode 100644
index 0000000..6afb850
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/res/layout/sink_activity.xml
@@ -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>
diff --git a/tests/AccessoryDisplay/sink/res/values/strings.xml b/tests/AccessoryDisplay/sink/res/values/strings.xml
new file mode 100644
index 0000000..29cd001
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Accessory Display Sink</string>
+</resources>
diff --git a/tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml b/tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml
new file mode 100644
index 0000000..e8fe929
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/res/xml/usb_device_filter.xml
@@ -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>
diff --git a/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/DisplaySinkService.java b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/DisplaySinkService.java
new file mode 100644
index 0000000..daec845
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/DisplaySinkService.java
@@ -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);
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java
new file mode 100644
index 0000000..6fe2cfb
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java
@@ -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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryBulkTransport.java b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryBulkTransport.java
new file mode 100644
index 0000000..a15bfad
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryBulkTransport.java
@@ -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.");
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryConstants.java b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryConstants.java
new file mode 100644
index 0000000..8197d6b
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbAccessoryConstants.java
@@ -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() {
+ }
+}
diff --git a/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbHid.java b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbHid.java
new file mode 100644
index 0000000..b4fa1fd
--- /dev/null
+++ b/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/UsbHid.java
@@ -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;
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/source/Android.mk b/tests/AccessoryDisplay/source/Android.mk
new file mode 100644
index 0000000..5d1085d
--- /dev/null
+++ b/tests/AccessoryDisplay/source/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the application.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := AccessoryDisplaySource
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := AccessoryDisplayCommon
+include $(BUILD_PACKAGE)
diff --git a/tests/AccessoryDisplay/source/AndroidManifest.xml b/tests/AccessoryDisplay/source/AndroidManifest.xml
new file mode 100644
index 0000000..d3edcb8
--- /dev/null
+++ b/tests/AccessoryDisplay/source/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.accessorydisplay.source" >
+
+ <uses-feature android:name="android.hardware.usb.accessory"/>
+ <uses-sdk android:minSdkVersion="18" />
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_app"
+ android:hardwareAccelerated="true">
+
+ <activity android:name=".SourceActivity"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.Holo">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
+ android:resource="@xml/usb_accessory_filter"/>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/drawable-mdpi/ic_app.png
Binary files differ
diff --git a/tests/AccessoryDisplay/source/res/layout/presentation_content.xml b/tests/AccessoryDisplay/source/res/layout/presentation_content.xml
new file mode 100644
index 0000000..bf9566a
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/layout/presentation_content.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.opengl.GLSurfaceView android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <Button android:id="@+id/explode_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:text="Explode!" />
+</RelativeLayout>
diff --git a/tests/AccessoryDisplay/source/res/layout/source_activity.xml b/tests/AccessoryDisplay/source/res/layout/source_activity.xml
new file mode 100644
index 0000000..ff2b818
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/layout/source_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:id="@+id/logTextView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</LinearLayout>
diff --git a/tests/AccessoryDisplay/source/res/values/strings.xml b/tests/AccessoryDisplay/source/res/values/strings.xml
new file mode 100644
index 0000000..0b488df
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Accessory Display Source</string>
+</resources>
diff --git a/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml b/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml
new file mode 100644
index 0000000..5313b4e
--- /dev/null
+++ b/tests/AccessoryDisplay/source/res/xml/usb_accessory_filter.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- Match all devices -->
+ <usb-accessory manufacturer="Android" model="Accessory Display" />
+</resources>
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java
new file mode 100644
index 0000000..ccead44
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/DisplaySourceService.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source;
+
+import com.android.accessorydisplay.common.Protocol;
+import com.android.accessorydisplay.common.Service;
+import com.android.accessorydisplay.common.Transport;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Display;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+
+public class DisplaySourceService extends Service {
+ private static final int MSG_DISPATCH_DISPLAY_ADDED = 1;
+ private static final int MSG_DISPATCH_DISPLAY_REMOVED = 2;
+
+ private static final String DISPLAY_NAME = "Accessory Display";
+ private static final int BIT_RATE = 6000000;
+ private static final int FRAME_RATE = 30;
+ private static final int I_FRAME_INTERVAL = 10;
+
+ private final Callbacks mCallbacks;
+ private final ServiceHandler mHandler;
+ private final DisplayManager mDisplayManager;
+
+ private boolean mSinkAvailable;
+ private int mSinkWidth;
+ private int mSinkHeight;
+ private int mSinkDensityDpi;
+
+ private VirtualDisplayThread mVirtualDisplayThread;
+
+ public DisplaySourceService(Context context, Transport transport, Callbacks callbacks) {
+ super(context, transport, Protocol.DisplaySourceService.ID);
+ mCallbacks = callbacks;
+ mHandler = new ServiceHandler();
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ }
+
+ @Override
+ public void start() {
+ super.start();
+
+ getLogger().log("Sending MSG_QUERY.");
+ getTransport().sendMessage(Protocol.DisplaySinkService.ID,
+ Protocol.DisplaySinkService.MSG_QUERY, null);
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+
+ handleSinkNotAvailable();
+ }
+
+ @Override
+ public void onMessageReceived(int service, int what, ByteBuffer content) {
+ switch (what) {
+ case Protocol.DisplaySourceService.MSG_SINK_AVAILABLE: {
+ getLogger().log("Received MSG_SINK_AVAILABLE");
+ if (content.remaining() >= 12) {
+ final int width = content.getInt();
+ final int height = content.getInt();
+ final int densityDpi = content.getInt();
+ if (width >= 0 && width <= 4096
+ && height >= 0 && height <= 4096
+ && densityDpi >= 60 && densityDpi <= 640) {
+ handleSinkAvailable(width, height, densityDpi);
+ return;
+ }
+ }
+ getLogger().log("Receive invalid MSG_SINK_AVAILABLE message.");
+ break;
+ }
+
+ case Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE: {
+ getLogger().log("Received MSG_SINK_NOT_AVAILABLE");
+ handleSinkNotAvailable();
+ break;
+ }
+ }
+ }
+
+ private void handleSinkAvailable(int width, int height, int densityDpi) {
+ if (mSinkAvailable && mSinkWidth == width && mSinkHeight == height
+ && mSinkDensityDpi == densityDpi) {
+ return;
+ }
+
+ getLogger().log("Accessory display sink available: "
+ + "width=" + width + ", height=" + height
+ + ", densityDpi=" + densityDpi);
+ mSinkAvailable = true;
+ mSinkWidth = width;
+ mSinkHeight = height;
+ mSinkDensityDpi = densityDpi;
+ createVirtualDisplay();
+ }
+
+ private void handleSinkNotAvailable() {
+ getLogger().log("Accessory display sink not available.");
+
+ mSinkAvailable = false;
+ mSinkWidth = 0;
+ mSinkHeight = 0;
+ mSinkDensityDpi = 0;
+ releaseVirtualDisplay();
+ }
+
+ private void createVirtualDisplay() {
+ releaseVirtualDisplay();
+
+ mVirtualDisplayThread = new VirtualDisplayThread(
+ mSinkWidth, mSinkHeight, mSinkDensityDpi);
+ mVirtualDisplayThread.start();
+ }
+
+ private void releaseVirtualDisplay() {
+ if (mVirtualDisplayThread != null) {
+ mVirtualDisplayThread.quit();
+ mVirtualDisplayThread = null;
+ }
+ }
+
+ public interface Callbacks {
+ public void onDisplayAdded(Display display);
+ public void onDisplayRemoved(Display display);
+ }
+
+ private final class ServiceHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_DISPLAY_ADDED: {
+ mCallbacks.onDisplayAdded((Display)msg.obj);
+ break;
+ }
+
+ case MSG_DISPATCH_DISPLAY_REMOVED: {
+ mCallbacks.onDisplayRemoved((Display)msg.obj);
+ break;
+ }
+ }
+ }
+ }
+
+ private final class VirtualDisplayThread extends Thread {
+ private static final int TIMEOUT_USEC = 1000000;
+
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+
+ private volatile boolean mQuitting;
+
+ public VirtualDisplayThread(int width, int height, int densityDpi) {
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+ }
+
+ @Override
+ public void run() {
+ MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL);
+
+ MediaCodec codec = MediaCodec.createEncoderByType("video/avc");
+ codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ Surface surface = codec.createInputSurface();
+ codec.start();
+
+ VirtualDisplay virtualDisplay = mDisplayManager.createPrivateVirtualDisplay(
+ DISPLAY_NAME, mWidth, mHeight, mDensityDpi, surface);
+ if (virtualDisplay != null) {
+ mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_ADDED,
+ virtualDisplay.getDisplay()).sendToTarget();
+
+ stream(codec);
+
+ mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_REMOVED,
+ virtualDisplay.getDisplay()).sendToTarget();
+ virtualDisplay.release();
+ }
+
+ codec.signalEndOfInputStream();
+ codec.stop();
+ }
+
+ public void quit() {
+ mQuitting = true;
+ }
+
+ private void stream(MediaCodec codec) {
+ BufferInfo info = new BufferInfo();
+ ByteBuffer[] buffers = null;
+ while (!mQuitting) {
+ int index = codec.dequeueOutputBuffer(info, TIMEOUT_USEC);
+ if (index >= 0) {
+ if (buffers == null) {
+ buffers = codec.getOutputBuffers();
+ }
+
+ ByteBuffer buffer = buffers[index];
+ buffer.limit(info.offset + info.size);
+ buffer.position(info.offset);
+
+ getTransport().sendMessage(Protocol.DisplaySinkService.ID,
+ Protocol.DisplaySinkService.MSG_CONTENT, buffer);
+ codec.releaseOutputBuffer(index, false);
+ } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ buffers = null;
+ } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ getLogger().log("Codec dequeue buffer timed out.");
+ }
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java
new file mode 100644
index 0000000..c59c958
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/SourceActivity.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.source.presentation.DemoPresentation;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.Display;
+import android.widget.TextView;
+
+public class SourceActivity extends Activity {
+ private static final String TAG = "SourceActivity";
+
+ private static final String ACTION_USB_ACCESSORY_PERMISSION =
+ "com.android.accessorydisplay.source.ACTION_USB_ACCESSORY_PERMISSION";
+
+ private static final String MANUFACTURER = "Android";
+ private static final String MODEL = "Accessory Display";
+
+ private UsbManager mUsbManager;
+ private AccessoryReceiver mReceiver;
+ private TextView mLogTextView;
+ private Logger mLogger;
+ private Presenter mPresenter;
+
+ private boolean mConnected;
+ private UsbAccessory mAccessory;
+ private UsbAccessoryStreamTransport mTransport;
+
+ private DisplaySourceService mDisplaySourceService;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+
+ setContentView(R.layout.source_activity);
+
+ mLogTextView = (TextView) findViewById(R.id.logTextView);
+ mLogTextView.setMovementMethod(ScrollingMovementMethod.getInstance());
+ mLogger = new TextLogger();
+ mPresenter = new Presenter();
+
+ mLogger.log("Waiting for accessory display sink to be attached to USB...");
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+ filter.addAction(ACTION_USB_ACCESSORY_PERMISSION);
+ mReceiver = new AccessoryReceiver();
+ registerReceiver(mReceiver, filter);
+
+ Intent intent = getIntent();
+ if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ UsbAccessory accessory =
+ (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ onAccessoryAttached(accessory);
+ }
+ } else {
+ UsbAccessory[] accessories = mUsbManager.getAccessoryList();
+ if (accessories != null) {
+ for (UsbAccessory accessory : accessories) {
+ onAccessoryAttached(accessory);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ //new DemoPresentation(this, getWindowManager().getDefaultDisplay()).show();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ private void onAccessoryAttached(UsbAccessory accessory) {
+ mLogger.log("USB accessory attached: " + accessory);
+ if (!mConnected) {
+ connect(accessory);
+ }
+ }
+
+ private void onAccessoryDetached(UsbAccessory accessory) {
+ mLogger.log("USB accessory detached: " + accessory);
+ if (mConnected && accessory.equals(mAccessory)) {
+ disconnect();
+ }
+ }
+
+ private void connect(UsbAccessory accessory) {
+ if (!isSink(accessory)) {
+ mLogger.log("Not connecting to USB accessory because it is not an accessory display sink: "
+ + accessory);
+ return;
+ }
+
+ if (mConnected) {
+ disconnect();
+ }
+
+ // Check whether we have permission to access the accessory.
+ if (!mUsbManager.hasPermission(accessory)) {
+ mLogger.log("Prompting the user for access to the accessory.");
+ Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION);
+ intent.setPackage(getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ mUsbManager.requestPermission(accessory, pendingIntent);
+ return;
+ }
+
+ // Open the accessory.
+ ParcelFileDescriptor fd = mUsbManager.openAccessory(accessory);
+ if (fd == null) {
+ mLogger.logError("Could not obtain accessory connection.");
+ return;
+ }
+
+ // All set.
+ mLogger.log("Connected.");
+ mConnected = true;
+ mAccessory = accessory;
+ mTransport = new UsbAccessoryStreamTransport(mLogger, fd);
+ startServices();
+ mTransport.startReading();
+ }
+
+ private void disconnect() {
+ mLogger.log("Disconnecting from accessory: " + mAccessory);
+ stopServices();
+
+ mLogger.log("Disconnected.");
+ mConnected = false;
+ mAccessory = null;
+ if (mTransport != null) {
+ mTransport.close();
+ mTransport = null;
+ }
+ }
+
+ private void startServices() {
+ mDisplaySourceService = new DisplaySourceService(this, mTransport, mPresenter);
+ mDisplaySourceService.start();
+ }
+
+ private void stopServices() {
+ if (mDisplaySourceService != null) {
+ mDisplaySourceService.stop();
+ mDisplaySourceService = null;
+ }
+ }
+
+ private static boolean isSink(UsbAccessory accessory) {
+ return MANUFACTURER.equals(accessory.getManufacturer())
+ && MODEL.equals(accessory.getModel());
+ }
+
+ class TextLogger extends Logger {
+ @Override
+ public void log(final String message) {
+ Log.d(TAG, message);
+
+ mLogTextView.post(new Runnable() {
+ @Override
+ public void run() {
+ mLogTextView.append(message);
+ mLogTextView.append("\n");
+ }
+ });
+ }
+ }
+
+ class AccessoryReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ UsbAccessory accessory = intent.<UsbAccessory>getParcelableExtra(
+ UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ String action = intent.getAction();
+ if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ onAccessoryAttached(accessory);
+ } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) {
+ onAccessoryDetached(accessory);
+ } else if (action.equals(ACTION_USB_ACCESSORY_PERMISSION)) {
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+ mLogger.log("Accessory permission granted: " + accessory);
+ onAccessoryAttached(accessory);
+ } else {
+ mLogger.logError("Accessory permission denied: " + accessory);
+ }
+ }
+ }
+ }
+ }
+
+ class Presenter implements DisplaySourceService.Callbacks {
+ private DemoPresentation mPresentation;
+
+ @Override
+ public void onDisplayAdded(Display display) {
+ mLogger.log("Accessory display added: " + display);
+
+ mPresentation = new DemoPresentation(SourceActivity.this, display, mLogger);
+ mPresentation.show();
+ }
+
+ @Override
+ public void onDisplayRemoved(Display display) {
+ mLogger.log("Accessory display removed: " + display);
+
+ if (mPresentation != null) {
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+ }
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java
new file mode 100644
index 0000000..c28f4359
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/UsbAccessoryStreamTransport.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.common.Transport;
+
+import android.hardware.usb.UsbAccessory;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Sends or receives messages over a file descriptor associated with a {@link UsbAccessory}.
+ */
+public class UsbAccessoryStreamTransport extends Transport {
+ private ParcelFileDescriptor mFd;
+ private FileInputStream mInputStream;
+ private FileOutputStream mOutputStream;
+
+ public UsbAccessoryStreamTransport(Logger logger, ParcelFileDescriptor fd) {
+ super(logger, 16384);
+ mFd = fd;
+ mInputStream = new FileInputStream(fd.getFileDescriptor());
+ mOutputStream = new FileOutputStream(fd.getFileDescriptor());
+ }
+
+ @Override
+ protected void ioClose() {
+ try {
+ mFd.close();
+ } catch (IOException ex) {
+ }
+ mFd = null;
+ mInputStream = null;
+ mOutputStream = null;
+ }
+
+ @Override
+ protected int ioRead(byte[] buffer, int offset, int count) throws IOException {
+ if (mInputStream == null) {
+ throw new IOException("Stream was closed.");
+ }
+ return mInputStream.read(buffer, offset, count);
+ }
+
+ @Override
+ protected void ioWrite(byte[] buffer, int offset, int count) throws IOException {
+ if (mOutputStream == null) {
+ throw new IOException("Stream was closed.");
+ }
+ mOutputStream.write(buffer, offset, count);
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java
new file mode 100644
index 0000000..51d8da9
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/Cube.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source.presentation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A vertex shaded cube.
+ */
+class Cube
+{
+ public Cube()
+ {
+ int one = 0x10000;
+ int vertices[] = {
+ -one, -one, -one,
+ one, -one, -one,
+ one, one, -one,
+ -one, one, -one,
+ -one, -one, one,
+ one, -one, one,
+ one, one, one,
+ -one, one, one,
+ };
+
+ int colors[] = {
+ 0, 0, 0, one,
+ one, 0, 0, one,
+ one, one, 0, one,
+ 0, one, 0, one,
+ 0, 0, one, one,
+ one, 0, one, one,
+ one, one, one, one,
+ 0, one, one, one,
+ };
+
+ byte indices[] = {
+ 0, 4, 5, 0, 5, 1,
+ 1, 5, 6, 1, 6, 2,
+ 2, 6, 7, 2, 7, 3,
+ 3, 7, 4, 3, 4, 0,
+ 4, 7, 6, 4, 6, 5,
+ 3, 0, 1, 3, 1, 2
+ };
+
+ // Buffers to be passed to gl*Pointer() functions
+ // must be direct, i.e., they must be placed on the
+ // native heap where the garbage collector cannot
+ // move them.
+ //
+ // Buffers with multi-byte datatypes (e.g., short, int, float)
+ // must have their byte order set to native order
+
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
+ vbb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = vbb.asIntBuffer();
+ mVertexBuffer.put(vertices);
+ mVertexBuffer.position(0);
+
+ ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
+ cbb.order(ByteOrder.nativeOrder());
+ mColorBuffer = cbb.asIntBuffer();
+ mColorBuffer.put(colors);
+ mColorBuffer.position(0);
+
+ mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
+ mIndexBuffer.put(indices);
+ mIndexBuffer.position(0);
+ }
+
+ public void draw(GL10 gl)
+ {
+ gl.glFrontFace(GL10.GL_CW);
+ gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
+ gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
+ gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
+ }
+
+ private IntBuffer mVertexBuffer;
+ private IntBuffer mColorBuffer;
+ private ByteBuffer mIndexBuffer;
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java
new file mode 100644
index 0000000..51dc82a
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/CubeRenderer.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source.presentation;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLSurfaceView;
+
+/**
+ * Render a pair of tumbling cubes.
+ */
+
+public class CubeRenderer implements GLSurfaceView.Renderer {
+ private boolean mTranslucentBackground;
+ private Cube mCube;
+ private float mAngle;
+ private float mScale = 1.0f;
+ private boolean mExploding;
+
+ public CubeRenderer(boolean useTranslucentBackground) {
+ mTranslucentBackground = useTranslucentBackground;
+ mCube = new Cube();
+ }
+
+ public void explode() {
+ mExploding = true;
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ /*
+ * Usually, the first thing one might want to do is to clear
+ * the screen. The most efficient way of doing this is to use
+ * glClear().
+ */
+
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ /*
+ * Now we're ready to draw some 3D objects
+ */
+
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+ gl.glTranslatef(0, 0, -3.0f);
+ gl.glRotatef(mAngle, 0, 1, 0);
+ gl.glRotatef(mAngle*0.25f, 1, 0, 0);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
+
+ gl.glScalef(mScale, mScale, mScale);
+ mCube.draw(gl);
+
+ gl.glRotatef(mAngle*2.0f, 0, 1, 1);
+ gl.glTranslatef(0.5f, 0.5f, 0.5f);
+
+ mCube.draw(gl);
+
+ mAngle += 1.2f;
+
+ if (mExploding) {
+ mScale *= 1.02f;
+ if (mScale > 4.0f) {
+ mScale = 1.0f;
+ mExploding = false;
+ }
+ }
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ gl.glViewport(0, 0, width, height);
+
+ /*
+ * Set our projection matrix. This doesn't have to be done
+ * each time we draw, but usually a new projection needs to
+ * be set when the viewport is resized.
+ */
+
+ float ratio = (float) width / height;
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ /*
+ * Some one-time OpenGL initialization can be made here
+ * probably based on features of this particular context
+ */
+ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+ GL10.GL_FASTEST);
+
+ if (mTranslucentBackground) {
+ gl.glClearColor(0,0,0,0);
+ } else {
+ gl.glClearColor(1,1,1,1);
+ }
+ gl.glEnable(GL10.GL_CULL_FACE);
+ gl.glShadeModel(GL10.GL_SMOOTH);
+ gl.glEnable(GL10.GL_DEPTH_TEST);
+ }
+}
diff --git a/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java
new file mode 100644
index 0000000..517b7fc
--- /dev/null
+++ b/tests/AccessoryDisplay/source/src/com/android/accessorydisplay/source/presentation/DemoPresentation.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.accessorydisplay.source.presentation;
+
+import com.android.accessorydisplay.common.Logger;
+import com.android.accessorydisplay.source.R;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * The presentation to show on the accessory display.
+ * <p>
+ * Note that this display may have different metrics from the display on which
+ * the main activity is showing so we must be careful to use the presentation's
+ * own {@link Context} whenever we load resources.
+ * </p>
+ */
+public final class DemoPresentation extends Presentation {
+ private final Logger mLogger;
+
+ private GLSurfaceView mSurfaceView;
+ private CubeRenderer mRenderer;
+ private Button mExplodeButton;
+
+ public DemoPresentation(Context context, Display display, Logger logger) {
+ super(context, display);
+ mLogger = logger;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Be sure to call the super class.
+ super.onCreate(savedInstanceState);
+
+ // Get the resources for the context of the presentation.
+ // Notice that we are getting the resources from the context of the presentation.
+ Resources r = getContext().getResources();
+
+ // Inflate the layout.
+ setContentView(R.layout.presentation_content);
+
+ // Set up the surface view for visual interest.
+ mRenderer = new CubeRenderer(false);
+ mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
+ mSurfaceView.setRenderer(mRenderer);
+
+ // Add a button.
+ mExplodeButton = (Button)findViewById(R.id.explode_button);
+ mExplodeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mRenderer.explode();
+ }
+ });
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mLogger.log("Received touch event: " + event);
+ return super.onTouchEvent(event);
+ }
+} \ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 375a160..1fbed3d 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -198,7 +198,6 @@ public class WifiNative {
/**
* Format of results:
* =================
- * id=1
* bssid=68:7f:74:d7:1b:6e
* freq=2412
* level=-43
@@ -209,11 +208,10 @@ public class WifiNative {
* ====
*
* RANGE=ALL gets all scan results
- * RANGE=ID- gets results from ID
* MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
*/
- public String scanResults(int sid) {
- return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
+ public String scanResults() {
+ return doStringCommand("BSS RANGE=ALL MASK=0x21987");
}
public boolean startDriver() {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 1040ab1..946bbad 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -68,8 +68,8 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
-import android.util.LruCache;
import android.text.TextUtils;
+import android.util.LruCache;
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
@@ -1339,7 +1339,6 @@ public class WifiStateMachine extends StateMachine {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private static final String ID_STR = "id=";
private static final String BSSID_STR = "bssid=";
private static final String FREQ_STR = "freq=";
private static final String LEVEL_STR = "level=";
@@ -1351,8 +1350,6 @@ public class WifiStateMachine extends StateMachine {
/**
* Format:
- *
- * id=1
* bssid=68:7f:76:d7:1a:6e
* freq=2412
* level=-44
@@ -1360,7 +1357,6 @@ public class WifiStateMachine extends StateMachine {
* flags=[WPA2-PSK-CCMP][WPS][ESS]
* ssid=zfdy
* ====
- * id=2
* bssid=68:5f:74:d7:1a:6f
* freq=5180
* level=-73
@@ -1369,43 +1365,16 @@ public class WifiStateMachine extends StateMachine {
* ssid=zuby
* ====
*/
- private void setScanResults() {
+ private void setScanResults(String scanResults) {
String bssid = "";
int level = 0;
int freq = 0;
long tsf = 0;
String flags = "";
WifiSsid wifiSsid = null;
- String scanResults;
- String tmpResults;
- StringBuffer scanResultsBuf = new StringBuffer();
- int sid = 0;
-
- while (true) {
- tmpResults = mWifiNative.scanResults(sid);
- if (TextUtils.isEmpty(tmpResults)) break;
- scanResultsBuf.append(tmpResults);
- scanResultsBuf.append("\n");
- String[] lines = tmpResults.split("\n");
- sid = -1;
- for (int i=lines.length - 1; i >= 0; i--) {
- if (lines[i].startsWith(END_STR)) {
- break;
- } else if (lines[i].startsWith(ID_STR)) {
- try {
- sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
- } catch (NumberFormatException e) {
- // Nothing to do
- }
- break;
- }
- }
- if (sid == -1) break;
- }
- scanResults = scanResultsBuf.toString();
- if (TextUtils.isEmpty(scanResults)) {
- return;
+ if (scanResults == null) {
+ return;
}
synchronized(mScanResultCache) {
@@ -2232,7 +2201,7 @@ public class WifiStateMachine extends StateMachine {
sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
break;
case WifiMonitor.SCAN_RESULTS_EVENT:
- setScanResults();
+ setScanResults(mWifiNative.scanResults());
sendScanResultsAvailableBroadcast();
mScanResultIsPending = false;
break;