Merge "Fix XML parsing crash in SettingsProvider"
diff --git a/api/current.txt b/api/current.txt
index 11db5c3..c43307e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -413,6 +413,7 @@
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -36925,6 +36926,26 @@
method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
}
+ public class WebMessage {
+ ctor public WebMessage(java.lang.String);
+ ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+ method public java.lang.String getData();
+ method public android.webkit.WebMessagePort[] getPorts();
+ }
+
+ public abstract class WebMessagePort {
+ ctor public WebMessagePort();
+ method public abstract void close();
+ method public abstract void postMessage(android.webkit.WebMessage);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+ }
+
+ public static abstract class WebMessagePort.WebMessageCallback {
+ ctor public WebMessagePort.WebMessageCallback();
+ method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+ }
+
public abstract class WebResourceError {
ctor public WebResourceError();
method public abstract java.lang.String getDescription();
@@ -37168,6 +37189,7 @@
method public android.webkit.WebBackForwardList copyBackForwardList();
method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public android.webkit.WebMessagePort[] createWebMessageChannel();
method public void destroy();
method public void documentHasImages(android.os.Message);
method public static void enableSlowWholeDocumentDraw();
@@ -37208,6 +37230,7 @@
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
+ method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public void postUrl(java.lang.String, byte[]);
method public void reload();
method public void removeJavascriptInterface(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index da5986f5..6acea9e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -485,6 +485,7 @@
field public static final int colorActivatedHighlight = 16843664; // 0x1010390
field public static final int colorBackground = 16842801; // 0x1010031
field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab
+ field public static final int colorBackgroundFloating = 16844007; // 0x10104e7
field public static final int colorButtonNormal = 16843819; // 0x101042b
field public static final int colorControlActivated = 16843818; // 0x101042a
field public static final int colorControlHighlight = 16843820; // 0x101042c
@@ -39154,6 +39155,26 @@
method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
}
+ public class WebMessage {
+ ctor public WebMessage(java.lang.String);
+ ctor public WebMessage(java.lang.String, android.webkit.WebMessagePort[]);
+ method public java.lang.String getData();
+ method public android.webkit.WebMessagePort[] getPorts();
+ }
+
+ public abstract class WebMessagePort {
+ ctor public WebMessagePort();
+ method public abstract void close();
+ method public abstract void postMessage(android.webkit.WebMessage);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback);
+ method public abstract void setWebMessageCallback(android.webkit.WebMessagePort.WebMessageCallback, android.os.Handler);
+ }
+
+ public static abstract class WebMessagePort.WebMessageCallback {
+ ctor public WebMessagePort.WebMessageCallback();
+ method public void onMessage(android.webkit.WebMessagePort, android.webkit.WebMessage);
+ }
+
public abstract class WebResourceError {
ctor public WebResourceError();
method public abstract java.lang.String getDescription();
@@ -39411,6 +39432,7 @@
method public android.webkit.WebBackForwardList copyBackForwardList();
method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public android.webkit.WebMessagePort[] createWebMessageChannel();
method public void destroy();
method public void documentHasImages(android.os.Message);
method public static void enableSlowWholeDocumentDraw();
@@ -39452,6 +39474,7 @@
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
+ method public void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public void postUrl(java.lang.String, byte[]);
method public void reload();
method public void removeJavascriptInterface(java.lang.String);
@@ -39664,6 +39687,7 @@
method public abstract void clearView();
method public abstract android.webkit.WebBackForwardList copyBackForwardList();
method public abstract android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
+ method public abstract android.webkit.WebMessagePort[] createWebMessageChannel();
method public abstract void destroy();
method public abstract void documentHasImages(android.os.Message);
method public abstract void dumpViewHierarchyWithProperties(java.io.BufferedWriter, int);
@@ -39710,6 +39734,7 @@
method public abstract boolean pageDown(boolean);
method public abstract boolean pageUp(boolean);
method public abstract void pauseTimers();
+ method public abstract void postMessageToMainFrame(android.webkit.WebMessage, android.net.Uri);
method public abstract void postUrl(java.lang.String, byte[]);
method public abstract void reload();
method public abstract void removeJavascriptInterface(java.lang.String);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 967e80c..0d35f9c 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -540,7 +540,7 @@
while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
int endPos = paraStart + breaks[breakIndex];
- boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
+ boolean moreChars = (endPos < bufEnd);
v = out(source, here, endPos,
fmAscent, fmDescent, fmTop, fmBottom,
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 69b4c47..51fefe9 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -102,7 +102,6 @@
private final float mLightRadius;
private final int mAmbientShadowAlpha;
private final int mSpotShadowAlpha;
- private final float mDensity;
private long mNativeProxy;
private boolean mInitialized = false;
@@ -119,7 +118,6 @@
(int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
a.recycle();
- mDensity = context.getResources().getDisplayMetrics().density;
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
@@ -128,10 +126,6 @@
AtlasInitializer.sInstance.init(context, mNativeProxy);
- // Setup timing
- mChoreographer = Choreographer.getInstance();
- nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
-
loadSystemProperties();
}
@@ -224,7 +218,7 @@
mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
lightX, mLightY, mLightZ, mLightRadius,
- mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
+ mAmbientShadowAlpha, mSpotShadowAlpha);
}
@Override
@@ -379,6 +373,7 @@
@Override
void setName(String name) {
+ nSetName(mNativeProxy, name);
}
@Override
@@ -487,15 +482,15 @@
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
private static native void nDeleteProxy(long nativeProxy);
- private static native void nSetFrameInterval(long nativeProxy, long frameIntervalNanos);
private static native boolean nLoadSystemProperties(long nativeProxy);
+ private static native void nSetName(long nativeProxy, String name);
private static native boolean nInitialize(long nativeProxy, Surface window);
private static native void nUpdateSurface(long nativeProxy, Surface window);
private static native boolean nPauseSurface(long nativeProxy, Surface window);
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius,
- int ambientShadowAlpha, int spotShadowAlpha, float density);
+ int ambientShadowAlpha, int spotShadowAlpha);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy);
diff --git a/core/java/android/webkit/WebMessage.java b/core/java/android/webkit/WebMessage.java
new file mode 100644
index 0000000..7683a40
--- /dev/null
+++ b/core/java/android/webkit/WebMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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.webkit;
+
+/**
+ * The Java representation of the HTML5 PostMessage event. See
+ * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
+ * for definition of a MessageEvent in HTML5.
+ *
+ */
+public class WebMessage {
+
+ private String mData;
+ private WebMessagePort[] mPorts;
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ */
+ public WebMessage(String data) {
+ mData = data;
+ }
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ * @param ports the ports that are sent with the message.
+ */
+ public WebMessage(String data, WebMessagePort[] ports) {
+ mData = data;
+ mPorts = ports;
+ }
+
+ /**
+ * Returns the data of the message.
+ */
+ public String getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the ports that are sent with the message, or null if no port
+ * is sent.
+ */
+ public WebMessagePort[] getPorts() {
+ return mPorts;
+ }
+}
diff --git a/core/java/android/webkit/WebMessagePort.java b/core/java/android/webkit/WebMessagePort.java
new file mode 100644
index 0000000..eab27bd
--- /dev/null
+++ b/core/java/android/webkit/WebMessagePort.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.webkit;
+
+import android.os.Handler;
+
+/**
+ * The Java representation of the HTML5 Message Port. See
+ * https://html.spec.whatwg.org/multipage/comms.html#messageport
+ * for definition of MessagePort in HTML5.
+ *
+ * A Message port represents one endpoint of a Message Channel. In Android
+ * webview, there is no separate Message Channel object. When a message channel
+ * is created, both ports are tangled to each other and started, and then
+ * returned in a MessagePort array, see {@link WebView#createWebMessageChannel}
+ * for creating a message channel.
+ *
+ * When a message port is first created or received via transfer, it does not
+ * have a WebMessageCallback to receive web messages. The messages are queued until
+ * a WebMessageCallback is set.
+ */
+public abstract class WebMessagePort {
+
+ /**
+ * The listener for handling MessagePort events. The message callback
+ * methods are called on the main thread. If the embedder application
+ * wants to receive the messages on a different thread, it can do this
+ * by passing a Handler in
+ * {@link WebMessagePort#setWebMessageCallback(WebMessageCallback, Handler)}.
+ * In the latter case, the application should be extra careful for thread safety
+ * since WebMessagePort methods should be called on main thread.
+ */
+ public static abstract class WebMessageCallback {
+ /**
+ * Message callback for receiving onMessage events.
+ *
+ * @param port the WebMessagePort that the message is destined for
+ * @param message the message from the entangled port.
+ */
+ public void onMessage(WebMessagePort port, WebMessage message) { }
+ }
+
+ /**
+ * Post a WebMessage to the entangled port.
+ *
+ * @param message the message from Java to JS.
+ *
+ * @throws IllegalStateException If message port is already transferred or closed.
+ */
+ public abstract void postMessage(WebMessage message);
+
+ /**
+ * Close the message port and free any resources associated with it.
+ */
+ public abstract void close();
+
+ /**
+ * Sets a callback to receive message events on the main thread.
+ *
+ * @param callback the message callback.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback);
+
+ /**
+ * Sets a callback to receive message events on the handler that is provided
+ * by the application.
+ *
+ * @param callback the message callback.
+ * @param handler the handler to receive the message messages.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback, Handler handler);
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 01a506c..67ad642 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
@@ -1825,6 +1826,37 @@
}
/**
+ * Creates a message channel to communicate with JS and returns the message
+ * ports that represent the endpoints of this message channel. The HTML5 message
+ * channel functionality is described here:
+ * https://html.spec.whatwg.org/multipage/comms.html#messagechannel
+ *
+ * The returned message channels are entangled and already in started state.
+ *
+ * @return the two message ports that form the message channel.
+ */
+ public WebMessagePort[] createWebMessageChannel() {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "createWebMessageChannel");
+ return mProvider.createWebMessageChannel();
+ }
+
+ /**
+ * Post a message to main frame. The embedded application can restrict the
+ * messages to a certain target origin. See
+ * https://html.spec.whatwg.org/multipage/comms.html#posting-messages
+ * for how target origin can be used.
+ *
+ * @param message the WebMessage
+ * @param targetOrigin the target origin.
+ */
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin) {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "postMessageToMainFrame. TargetOrigin=" + targetOrigin);
+ mProvider.postMessageToMainFrame(message, targetOrigin);
+ }
+
+ /**
* Gets the WebSettings object used to control the settings for this
* WebView.
*
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 379a732..0cdb875 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -25,6 +25,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.print.PrintDocumentAdapter;
@@ -227,6 +228,10 @@
public void removeJavascriptInterface(String interfaceName);
+ public WebMessagePort[] createWebMessageChannel();
+
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
+
public WebSettings getSettings();
public void setMapTrackballToArrowKeys(boolean setMap);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 399f4c5..f676254 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -29,6 +29,8 @@
import android.os.IBinder;
import android.transition.Transition;
import android.transition.Transition.EpicenterCallback;
+import android.transition.Transition.TransitionListener;
+import android.transition.Transition.TransitionListenerAdapter;
import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
@@ -39,12 +41,13 @@
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
import java.lang.ref.WeakReference;
-import java.util.List;
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
@@ -96,14 +99,12 @@
private WindowManager mWindowManager;
private boolean mIsShowing;
+ private boolean mIsTransitioningToDismiss;
private boolean mIsDropdown;
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
- /** View that holds the popup background. May be the content view. */
- private View mBackgroundView;
-
/** The contents of the popup. */
private View mContentView;
@@ -1183,23 +1184,30 @@
+ "calling setContentView() before attempting to show the popup.");
}
- // When a background is available, we embed the content view within
- // another view that owns the background drawable.
- if (mBackground != null) {
- mBackgroundView = createBackgroundView(mContentView);
- mBackgroundView.setBackground(mBackground);
- } else {
- mBackgroundView = mContentView;
+ // The old decor view may be transitioning out. Make sure it finishes
+ // and cleans up before we try to create another one.
+ if (mDecorView != null) {
+ mDecorView.cancelTransitions();
}
- mDecorView = createDecorView(mBackgroundView);
+ // When a background is available, we embed the content view within
+ // another view that owns the background drawable.
+ final View backgroundView;
+ if (mBackground != null) {
+ backgroundView = createBackgroundView(mContentView);
+ backgroundView.setBackground(mBackground);
+ } else {
+ backgroundView = mContentView;
+ }
+
+ mDecorView = createDecorView(backgroundView);
// The background owner should be elevated so that it casts a shadow.
- mBackgroundView.setElevation(mElevation);
+ backgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
- final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
+ final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
@@ -1268,26 +1276,13 @@
p.packageName = mContext.getPackageName();
}
- final View rootView = mContentView.getRootView();
- rootView.setFitsSystemWindows(mLayoutInsetDecor);
+ final PopupDecorView decorView = mDecorView;
+ decorView.setFitsSystemWindows(mLayoutInsetDecor);
+ decorView.requestEnterTransition(mEnterTransition);
+
setLayoutDirectionFromAnchor();
- mWindowManager.addView(rootView, p);
-
- // Postpone enter transition until the scene root has been laid out.
- if (mEnterTransition != null) {
- mEnterTransition.addTarget(mBackgroundView);
- mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
- }
- });
-
- mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
- new PostLayoutTransitionListener(mDecorView, mEnterTransition));
- }
+ mWindowManager.addView(decorView, p);
}
private void setLayoutDirectionFromAnchor() {
@@ -1591,35 +1586,38 @@
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (!isShowing()) {
+ if (!isShowing() || mIsTransitioningToDismiss) {
return;
}
+ final PopupDecorView decorView = mDecorView;
+ final View contentView = mContentView;
+
+ final ViewGroup contentHolder;
+ final ViewParent contentParent = contentView.getParent();
+ if (contentParent instanceof ViewGroup) {
+ contentHolder = ((ViewGroup) contentParent);
+ } else {
+ contentHolder = null;
+ }
+
+ // Ensure any ongoing or pending transitions are canceled.
+ decorView.cancelTransitions();
+
unregisterForScrollChanged();
mIsShowing = false;
+ mIsTransitioningToDismiss = true;
- if (mExitTransition != null) {
- // Cache the content view, since it may change without notice.
- final View contentView = mContentView;
-
- mExitTransition.addTarget(mBackgroundView);
- mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
+ if (mExitTransition != null && decorView.isLaidOut()) {
+ decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
-
- dismissImmediate(contentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
});
-
- TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);
-
- // Transition to invisible.
- mBackgroundView.setVisibility(View.INVISIBLE);
} else {
- dismissImmediate(mContentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
if (mOnDismissListener != null) {
@@ -1631,24 +1629,22 @@
* Removes the popup from the window manager and tears down the supporting
* view hierarchy, if necessary.
*/
- private void dismissImmediate(View contentView) {
- if (mDecorView == null || mBackgroundView == null) {
- throw new RuntimeException("Popup window already dismissed");
+ private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
+ // If this method gets called and the decor view doesn't have a parent,
+ // then it was either never added or was already removed. That should
+ // never happen, but it's worth checking to avoid potential crashes.
+ if (decorView.getParent() != null) {
+ mWindowManager.removeViewImmediate(decorView);
}
- try {
- if (mDecorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mDecorView);
- }
- } finally {
- mDecorView.removeView(mBackgroundView);
- mDecorView = null;
-
- if (mBackgroundView != contentView) {
- ((ViewGroup) mBackgroundView).removeView(contentView);
- }
- mBackgroundView = null;
+ if (contentHolder != null) {
+ contentHolder.removeView(contentView);
}
+
+ // This needs to stay until after all transitions have ended since we
+ // need the reference to cancel transitions in preparePopup().
+ mDecorView = null;
+ mIsTransitioningToDismiss = false;
}
/**
@@ -1909,47 +1905,9 @@
mAnchoredGravity = gravity;
}
- /**
- * Layout listener used to run a transition immediately after a view is
- * laid out. Forces the view to transition from invisible to visible.
- */
- private static class PostLayoutTransitionListener implements
- ViewTreeObserver.OnGlobalLayoutListener {
- private final ViewGroup mSceneRoot;
- private final Transition mTransition;
-
- public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
- mSceneRoot = sceneRoot;
- mTransition = transition;
- }
-
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
- if (observer == null) {
- // View has been detached.
- return;
- }
-
- observer.removeOnGlobalLayoutListener(this);
-
- // Set all targets to be initially invisible.
- final List<View> targets = mTransition.getTargets();
- final int N = targets.size();
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.INVISIBLE);
- }
-
- TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
-
- // Transition targets to visible.
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.VISIBLE);
- }
- }
- }
-
private class PopupDecorView extends FrameLayout {
+ private TransitionListenerAdapter mPendingExitListener;
+
public PopupDecorView(Context context) {
super(context);
}
@@ -2004,6 +1962,100 @@
return super.onTouchEvent(event);
}
}
+
+ /**
+ * Requests that an enter transition run after the next layout pass.
+ */
+ public void requestEnterTransition(Transition transition) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null && transition != null) {
+ final Transition enterTransition = transition.clone();
+
+ // Postpone the enter transition after the first layout pass.
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+
+ startEnterTransition(enterTransition);
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts the pending enter transition, if one is set.
+ */
+ private void startEnterTransition(Transition enterTransition) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ enterTransition.addTarget(child);
+ child.setVisibility(View.INVISIBLE);
+ }
+
+ TransitionManager.beginDelayedTransition(this, enterTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Starts an exit transition immediately.
+ * <p>
+ * <strong>Note:</strong> The transition listener is guaranteed to have
+ * its {@code onTransitionEnd} method called even if the transition
+ * never starts; however, it may be called with a {@code null} argument.
+ */
+ public void startExitTransition(Transition transition, final TransitionListener listener) {
+ if (transition == null) {
+ return;
+ }
+
+ // The exit listener MUST be called for cleanup, even if the
+ // transition never starts or ends. Stash it for later.
+ mPendingExitListener = new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ listener.onTransitionEnd(transition);
+
+ // The listener was called. Our job here is done.
+ mPendingExitListener = null;
+ }
+ };
+
+ final Transition exitTransition = transition.clone();
+ exitTransition.addListener(mPendingExitListener);
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ exitTransition.addTarget(child);
+ }
+
+ TransitionManager.beginDelayedTransition(this, exitTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Cancels all pending or current transitions.
+ */
+ public void cancelTransitions() {
+ TransitionManager.endTransitions(this);
+
+ if (mPendingExitListener != null) {
+ mPendingExitListener.onTransitionEnd(null);
+ }
+ }
}
private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 3592687..e7031fe 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -727,14 +727,10 @@
}
}
- if (scheduleOtherSpellCheck && wordStart <= end) {
+ if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
// Update range span: start new spell check from last wordStart
setRangeSpan(editable, wordStart, end);
} else {
- if (DBG && scheduleOtherSpellCheck) {
- Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
- + wordStart + " to " + end);
- }
removeRangeSpan(editable);
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 2b20b38..7d45071 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -43,8 +43,6 @@
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
View.OnAttachStateChangeListener, MenuPresenter {
- private static final String TAG = "MenuPopupHelper";
-
static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
private final Context mContext;
@@ -132,7 +130,18 @@
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by
+ * {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
@@ -169,6 +178,7 @@
}
}
+ @Override
public void onDismiss() {
mPopup = null;
mMenu.close();
@@ -190,6 +200,7 @@
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 39064ed..fff8604 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -489,7 +489,7 @@
proxy->initialize(surface);
// Shadows can't be used via this interface, so just set the light source
// to all 0s. (and width & height are unused, TODO remove them)
- proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0, 1.0f);
+ proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
return (jlong) proxy;
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ad93301..3d9a9ed 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -239,18 +239,20 @@
delete proxy;
}
-static void android_view_ThreadedRenderer_setFrameInterval(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlong frameIntervalNanos) {
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setFrameInterval(frameIntervalNanos);
-}
-
static jboolean android_view_ThreadedRenderer_loadSystemProperties(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
return proxy->loadSystemProperties();
}
+static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jstring jname) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ const char* name = env->GetStringUTFChars(jname, NULL);
+ proxy->setName(name);
+ env->ReleaseStringUTFChars(jname, name);
+}
+
static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -284,7 +286,7 @@
jint ambientShadowAlpha, jint spotShadowAlpha, jfloat density) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius,
- ambientShadowAlpha, spotShadowAlpha, density);
+ ambientShadowAlpha, spotShadowAlpha);
}
static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -424,12 +426,12 @@
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
- { "nSetFrameInterval", "(JJ)V", (void*) android_view_ThreadedRenderer_setFrameInterval },
{ "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
+ { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
{ "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
- { "nSetup", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup },
+ { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
{ "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 30ce271..15797dd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -31,8 +31,10 @@
<attr name="colorForeground" format="color" />
<!-- Default color of foreground imagery on an inverted background. -->
<attr name="colorForegroundInverse" format="color" />
- <!-- Color that matches (as closely as possible) the window background. -->
+ <!-- Default color of background imagery, ex. full-screen windows. -->
<attr name="colorBackground" format="color" />
+ <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->
+ <attr name="colorBackgroundFloating" format="color" />
<!-- This is a hint for a solid color that can be used for caching
rendered views. This should be the color of the background when
there is a solid background color; it should be null when the
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 16f0676..da911b2 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2642,15 +2642,11 @@
<public type="style" name="Theme.Material.Light.LightStatusBar" />
<public type="style" name="ThemeOverlay.Material.Dialog" />
- <!-- Context menu ID for the "Paste as plain text" menu item to to copy the current contents
- of the clipboard into the text view without formatting. -->
<public type="id" name="pasteAsPlainText" />
- <!-- Context menu ID for the "Undo" menu item to undo the last text edit operation. -->
<public type="id" name="undo" />
- <!-- Context menu ID for the "Redo" menu item to redo the last text edit operation. -->
<public type="id" name="redo" />
-
- <!-- TextView attribute to control undo behavior. -->
<public type="attr" name="allowUndo" />
+ <public type="attr" name="colorBackgroundFloating" />
+
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 4ba6c0b..9e87b4d 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -45,6 +45,7 @@
<item name="colorForeground">@color/bright_foreground_dark</item>
<item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item>
<item name="colorBackground">@color/background_dark</item>
+ <item name="colorBackgroundFloating">?attr/colorBackground</item>
<item name="colorBackgroundCacheHint">?attr/colorBackground</item>
<item name="colorPressedHighlight">@color/legacy_pressed_highlight</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index c30b3d5..701d0ef 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -65,6 +65,7 @@
<item name="colorForeground">@color/bright_foreground_holo_dark</item>
<item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_dark</item>
<item name="colorBackground">@color/background_holo_dark</item>
+ <item name="colorBackgroundFloating">@color/background_holo_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_dark</item>
<item name="disabledAlpha">0.5</item>
<item name="backgroundDimAmount">0.6</item>
@@ -404,6 +405,7 @@
<item name="colorForeground">@color/bright_foreground_holo_light</item>
<item name="colorForegroundInverse">@color/bright_foreground_inverse_holo_light</item>
<item name="colorBackground">@color/background_holo_light</item>
+ <item name="colorBackgroundFloating">@color/background_holo_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_holo_light</item>
<item name="disabledAlpha">0.5</item>
<item name="backgroundDimAmount">0.6</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index a610d07..38cfecd 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -45,6 +45,7 @@
<item name="colorForeground">@color/foreground_material_dark</item>
<item name="colorForegroundInverse">@color/foreground_material_light</item>
<item name="colorBackground">@color/background_material_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
@@ -398,6 +399,7 @@
<item name="colorForeground">@color/foreground_material_light</item>
<item name="colorForegroundInverse">@color/foreground_material_dark</item>
<item name="colorBackground">@color/background_material_light</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
<item name="backgroundDimAmount">0.6</item>
@@ -770,6 +772,7 @@
<item name="colorForeground">@color/foreground_material_light</item>
<item name="colorForegroundInverse">@color/foreground_material_dark</item>
<item name="colorBackground">@color/background_material_light</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
<item name="textColorPrimary">@color/primary_text_material_light</item>
@@ -806,6 +809,7 @@
<item name="colorForeground">@color/foreground_material_dark</item>
<item name="colorForegroundInverse">@color/foreground_material_light</item>
<item name="colorBackground">@color/background_material_dark</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
<item name="textColorPrimary">@color/primary_text_material_dark</item>
@@ -852,7 +856,6 @@
<!-- Theme overlay that overrides window properties to display as a dialog. -->
<style name="ThemeOverlay.Material.Dialog">
- <item name="colorBackground">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@null</item>
<item name="windowFrame">@null</item>
@@ -1044,7 +1047,7 @@
<eat-comment />
<style name="Theme.Material.BaseDialog">
- <item name="colorBackground">@color/background_floating_material_dark</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
@@ -1155,7 +1158,7 @@
<!-- Light material dialog themes -->
<style name="Theme.Material.Light.BaseDialog">
- <item name="colorBackground">@color/background_floating_material_light</item>
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 46b0945..7df61f27 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -97,8 +97,8 @@
int64_t totalDuration =
frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync];
uint32_t framebucket = std::min(
- static_cast<typeof sizeof(mFrameCounts)>(ns2ms(totalDuration)),
- sizeof(mFrameCounts) / sizeof(mFrameCounts[0]));
+ static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)),
+ mFrameCounts.size());
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
mFrameCounts[framebucket]++;
@@ -137,8 +137,8 @@
}
void JankTracker::reset() {
- memset(mBuckets, 0, sizeof(mBuckets));
- memset(mFrameCounts, 0, sizeof(mFrameCounts));
+ mBuckets.fill({0});
+ mFrameCounts.fill(0);
mTotalFrameCount = 0;
mJankFrameCount = 0;
}
@@ -146,7 +146,7 @@
uint32_t JankTracker::findPercentile(int percentile) {
int pos = percentile * mTotalFrameCount / 100;
int remaining = mTotalFrameCount - pos;
- for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) {
+ for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
remaining -= mFrameCounts[i];
if (remaining <= 0) {
return i;
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 3d4929b..ae339ec 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -20,6 +20,7 @@
#include "renderthread/TimeLord.h"
#include "utils/RingBuffer.h"
+#include <array>
#include <memory>
namespace android {
@@ -56,9 +57,9 @@
private:
uint32_t findPercentile(int p);
- JankBucket mBuckets[NUM_BUCKETS];
- int64_t mThresholds[NUM_BUCKETS];
- uint32_t mFrameCounts[128];
+ std::array<JankBucket, NUM_BUCKETS> mBuckets;
+ std::array<int64_t, NUM_BUCKETS> mThresholds;
+ std::array<uint32_t, 128> mFrameCounts;
int64_t mFrameInterval;
uint32_t mTotalFrameCount;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index fcf6eb2..9456073 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -51,6 +51,7 @@
, mRootRenderNode(rootRenderNode)
, mCurrentFrameInfo(nullptr) {
mRenderThread.renderState().registerCanvasContext(this);
+ mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
CanvasContext::~CanvasContext() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9a60dc7..c3904c2 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -33,6 +33,7 @@
#include <utils/Vector.h>
#include <set>
+#include <string>
namespace android {
namespace uirenderer {
@@ -106,6 +107,9 @@
void dumpFrames(int fd);
void resetFrameStats();
+ void setName(const std::string&& name) { mName = name; }
+ const std::string& name() { return mName; }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -139,6 +143,7 @@
FrameInfo* mCurrentFrameInfo;
// Ring buffer large enough for 1 second worth of frames
RingBuffer<FrameInfo, 60> mFrames;
+ std::string mName;
std::set<RenderNode*> mPrefetechedLayers;
};
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index f48ee41..35391b2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -34,7 +34,6 @@
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
- , mDensity(1.0f) // safe enough default
, mSyncResult(kSync_OK) {
}
@@ -84,7 +83,6 @@
void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
- mContext->profiler().setDensity(mDensity);
mContext->profiler().startFrame();
bool canUnblockUiThread;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 0e56bea..8039643 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -62,7 +62,6 @@
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
- void setDensity(float density) { mDensity = density; }
int drawFrame();
int64_t* frameInfo() { return mFrameInfo; }
@@ -83,7 +82,6 @@
/*********************************************
* Single frame data
*********************************************/
- float mDensity;
std::vector< sp<DeferredLayerUpdater> > mLayers;
int mSyncResult;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0fa2f23..ea4216c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -97,18 +97,6 @@
}
}
-CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
- args->thread->setFrameInterval(args->frameIntervalNanos);
- return nullptr;
-}
-
-void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) {
- SETUP_TASK(setFrameInterval);
- args->thread = &mRenderThread;
- args->frameIntervalNanos = frameIntervalNanos;
- post(task);
-}
-
CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) {
args->context->setSwapBehavior(args->swapBehavior);
return nullptr;
@@ -138,6 +126,18 @@
return (bool) postAndWait(task);
}
+CREATE_BRIDGE2(setName, CanvasContext* context, const char* name) {
+ args->context->setName(std::string(args->name));
+ return nullptr;
+}
+
+void RenderProxy::setName(const char* name) {
+ SETUP_TASK(setName);
+ args->context = mContext;
+ args->name = name;
+ post(task);
+}
+
CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
return (void*) args->context->initialize(args->window);
}
@@ -181,8 +181,7 @@
}
void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density) {
- mDrawFrameTask.setDensity(density);
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
SETUP_TASK(setup);
args->context = mContext;
args->width = width;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 19e73e5..43cbe07 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -62,16 +62,16 @@
ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
ANDROID_API virtual ~RenderProxy();
- ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos);
// Won't take effect until next EGLSurface creation
ANDROID_API void setSwapBehavior(SwapBehavior swapBehavior);
ANDROID_API bool loadSystemProperties();
+ ANDROID_API void setName(const char* name);
ANDROID_API bool initialize(const sp<ANativeWindow>& window);
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density);
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setOpaque(bool opaque);
ANDROID_API int64_t* frameInfo();
ANDROID_API int syncAndDrawFrame();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 2a8baa7..3ac2976 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -22,6 +22,8 @@
#include "RenderProxy.h"
#include <gui/DisplayEventReceiver.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
#include <sys/resource.h>
#include <utils/Log.h>
@@ -151,11 +153,6 @@
LOG_ALWAYS_FATAL("Can't destroy the render thread");
}
-void RenderThread::setFrameInterval(nsecs_t frameInterval) {
- mTimeLord.setFrameInterval(frameInterval);
- mJankTracker->setFrameInterval(frameInterval);
-}
-
void RenderThread::initializeDisplayEventReceiver() {
LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
mDisplayEventReceiver = new DisplayEventReceiver();
@@ -169,10 +166,16 @@
}
void RenderThread::initThreadLocals() {
+ sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
+ status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo);
+ LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
+ nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps);
+ mTimeLord.setFrameInterval(frameIntervalNanos);
initializeDisplayEventReceiver();
mEglManager = new EglManager(*this);
mRenderState = new RenderState(*this);
- mJankTracker = new JankTracker(mTimeLord.frameIntervalNanos());
+ mJankTracker = new JankTracker(frameIntervalNanos);
}
int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f169424..8096099 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -23,6 +23,7 @@
#include "TimeLord.h"
#include <cutils/compiler.h>
+#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
#include <utils/Mutex.h>
#include <utils/Singleton.h>
@@ -86,13 +87,13 @@
// the next vsync. If it is not currently registered this does nothing.
void pushBackFrameCallback(IFrameCallback* callback);
- void setFrameInterval(nsecs_t frameInterval);
-
TimeLord& timeLord() { return mTimeLord; }
RenderState& renderState() { return *mRenderState; }
EglManager& eglManager() { return *mEglManager; }
JankTracker& jankTracker() { return *mJankTracker; }
+ const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
+
protected:
virtual bool threadLoop() override;
@@ -122,6 +123,8 @@
nsecs_t mNextWakeup;
TaskQueue mQueue;
+ DisplayInfo mDisplayInfo;
+
DisplayEventReceiver* mDisplayEventReceiver;
bool mVsyncRequested;
std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index 0d1e63e..805989b 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -85,7 +85,7 @@
proxy->initialize(surface);
float lightX = width / 2.0;
proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
- dp(800.0f), 255 * 0.075, 255 * 0.15, gDisplay.density);
+ dp(800.0f), 255 * 0.075, 255 * 0.15);
android::uirenderer::Rect DUMMY;
diff --git a/media/java/android/media/midi/IMidiDeviceListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
index 17d9bfd..31c66e3 100644
--- a/media/java/android/media/midi/IMidiDeviceListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -17,10 +17,12 @@
package android.media.midi;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
/** @hide */
oneway interface IMidiDeviceListener
{
void onDeviceAdded(in MidiDeviceInfo device);
void onDeviceRemoved(in MidiDeviceInfo device);
+ void onDeviceStatusChanged(in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 3331aae..642078a 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -24,4 +24,7 @@
ParcelFileDescriptor openInputPort(IBinder token, int portNumber);
ParcelFileDescriptor openOutputPort(IBinder token, int portNumber);
void closePort(IBinder token);
+
+ // connects the input port pfd to the specified output port
+ void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
}
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index 617b03e..a3b40d6 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -19,6 +19,7 @@
import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
import android.os.Bundle;
import android.os.IBinder;
@@ -44,4 +45,11 @@
// used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
// manifest's meta-data
MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
+
+ // used for client's to retrieve a device's MidiDeviceStatus
+ MidiDeviceStatus getDeviceStatus(in MidiDeviceInfo deviceInfo);
+
+ // used by MIDI devices to report their status
+ // the token is used by MidiService for death notification
+ void setDeviceStatus(IBinder token, in MidiDeviceStatus status);
}
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index af0737d..569f7c6 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -26,6 +26,8 @@
import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.IOException;
@@ -44,8 +46,29 @@
private Context mContext;
private ServiceConnection mServiceConnection;
+
private final CloseGuard mGuard = CloseGuard.get();
+ public class MidiConnection implements Closeable {
+ private final IBinder mToken;
+ private final MidiInputPort mInputPort;
+
+ MidiConnection(IBinder token, MidiInputPort inputPort) {
+ mToken = token;
+ mInputPort = inputPort;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ mDeviceServer.closePort(mToken);
+ IoUtils.closeQuietly(mInputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in MidiConnection.close");
+ }
+ }
+ }
+
/* package */ MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
this(deviceInfo, server, null, null);
}
@@ -108,6 +131,36 @@
}
}
+ /**
+ * Connects the supplied {@link MidiInputPort} to the output port of this device
+ * with the specified port number. Once the connection is made, the MidiInput port instance
+ * can no longer receive data via its {@link MidiReciever.receive} method.
+ * This method returns a {@link #MidiConnection} object, which can be used to close the connection
+ * @param inputPort the inputPort to connect
+ * @param outputPortNumber the port number of the output port to connect inputPort to.
+ * @return {@link #MidiConnection} object if the connection is successful, or null in case of failure
+ */
+ public MidiConnection connectPorts(MidiInputPort inputPort, int outputPortNumber) {
+ if (outputPortNumber < 0 || outputPortNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("outputPortNumber out of range");
+ }
+
+ ParcelFileDescriptor pfd = inputPort.claimFileDescriptor();
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ IBinder token = new Binder();
+ mDeviceServer.connectPorts(token, pfd, outputPortNumber);
+ // close our copy of the file descriptor
+ IoUtils.closeQuietly(pfd);
+ return new MidiConnection(token, inputPort);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in connectPorts");
+ return null;
+ }
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3b4b6f0..b3c0e3a 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,8 +16,8 @@
package android.media.midi;
-import android.os.IBinder;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
@@ -61,7 +61,25 @@
private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
= new CopyOnWriteArrayList<MidiInputPort>();
+
+ // for reporting device status
+ private final IBinder mDeviceStatusToken = new Binder();
+ private final boolean[] mInputPortBusy;
+ private final int[] mOutputPortOpenCount;
+
private final CloseGuard mGuard = CloseGuard.get();
+ private boolean mIsClosed;
+
+ private final Callback mCallback;
+
+ public interface Callback {
+ /**
+ * Called to notify when an our device status has changed
+ * @param server the {@link MidiDeviceServer} that changed
+ * @param status the {@link MidiDeviceStatus} for the device
+ */
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
+ }
abstract private class PortClient implements IBinder.DeathRecipient {
final IBinder mToken;
@@ -96,7 +114,10 @@
void close() {
mToken.unlinkToDeath(this, 0);
synchronized (mInputPortOutputPorts) {
- mInputPortOutputPorts[mOutputPort.getPortNumber()] = null;
+ int portNumber = mOutputPort.getPortNumber();
+ mInputPortOutputPorts[portNumber] = null;
+ mInputPortBusy[portNumber] = false;
+ updateDeviceStatus();
}
IoUtils.closeQuietly(mOutputPort);
}
@@ -113,7 +134,15 @@
@Override
void close() {
mToken.unlinkToDeath(this, 0);
- mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort);
+ int portNumber = mInputPort.getPortNumber();
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().disconnect(mInputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
mInputPorts.remove(mInputPort);
IoUtils.closeQuietly(mInputPort);
}
@@ -153,6 +182,8 @@
synchronized (mPortClients) {
mPortClients.put(token, client);
}
+ mInputPortBusy[portNumber] = true;
+ updateDeviceStatus();
return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
@@ -178,7 +209,14 @@
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
- mOutputPortDispatchers[portNumber].getSender().connect(inputPort);
+ MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
+ synchronized (dispatcher) {
+ dispatcher.getSender().connect(inputPort);
+ int openCount = dispatcher.getReceiverCount();
+ mOutputPortOpenCount[portNumber] = openCount;
+ updateDeviceStatus();
+ }
+
mInputPorts.add(inputPort);
OutputPortClient client = new OutputPortClient(token, inputPort);
synchronized (mPortClients) {
@@ -200,14 +238,27 @@
}
}
}
+
+ @Override
+ public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
+ int outputPortNumber) {
+ MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
+ mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
+ mInputPorts.add(inputPort);
+ OutputPortClient client = new OutputPortClient(token, inputPort);
+ synchronized (mPortClients) {
+ mPortClients.put(token, client);
+ }
+ }
};
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
- int numOutputPorts) {
+ int numOutputPorts, Callback callback) {
mMidiManager = midiManager;
mInputPortReceivers = inputPortReceivers;
mInputPortCount = inputPortReceivers.length;
mOutputPortCount = numOutputPorts;
+ mCallback = callback;
mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
@@ -216,6 +267,9 @@
mOutputPortDispatchers[i] = new MidiDispatcher();
}
+ mInputPortBusy = new boolean[mInputPortCount];
+ mOutputPortOpenCount = new int[numOutputPorts];
+
mGuard.open("close");
}
@@ -230,9 +284,28 @@
mDeviceInfo = deviceInfo;
}
+ private void updateDeviceStatus() {
+ // clear calling identity, since we may be in a Binder call from one of our clients
+ long identityToken = Binder.clearCallingIdentity();
+
+ MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy,
+ mOutputPortOpenCount);
+ if (mCallback != null) {
+ mCallback.onDeviceStatusChanged(this, status);
+ }
+ try {
+ mMidiManager.setDeviceStatus(mDeviceStatusToken, status);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in updateDeviceStatus");
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
+ if (mIsClosed) return;
mGuard.close();
for (int i = 0; i < mInputPortCount; i++) {
@@ -251,6 +324,7 @@
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in unregisterDeviceServer");
}
+ mIsClosed = true;
}
}
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 64f69cd..5f55ae2 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -57,6 +57,13 @@
private MidiDeviceServer mServer;
private MidiDeviceInfo mDeviceInfo;
+ private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+ MidiDeviceService.this.onDeviceStatusChanged(status);
+ }
+ };
+
@Override
public void onCreate() {
mMidiManager = IMidiManager.Stub.asInterface(
@@ -75,7 +82,7 @@
inputPortReceivers = new MidiReceiver[0];
}
server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
- deviceInfo.getOutputPortCount());
+ deviceInfo.getOutputPortCount(), mCallback);
server.setDeviceInfo(deviceInfo);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
@@ -114,6 +121,13 @@
return mDeviceInfo;
}
+ /**
+ * Called to notify when an our {@link MidiDeviceStatus} has changed
+ * @param status the number of the port that was opened
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
+
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/midi/MidiDeviceStatus.aidl b/media/java/android/media/midi/MidiDeviceStatus.aidl
new file mode 100644
index 0000000..1a848c0
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015, 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.media.midi;
+
+parcelable MidiDeviceStatus;
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
new file mode 100644
index 0000000..cc04889
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.media.midi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is an immutable class that describes the current status of a MIDI device's ports.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public final class MidiDeviceStatus implements Parcelable {
+
+ private static final String TAG = "MidiDeviceStatus";
+
+ private final MidiDeviceInfo mDeviceInfo;
+ // true if input ports are busy
+ private final boolean mInputPortBusy[];
+ // open counts for output ports
+ private final int mOutputPortOpenCount[];
+
+ /**
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo, boolean inputPortBusy[],
+ int outputPortOpenCount[]) {
+ // MidiDeviceInfo is immutable so we can share references
+ mDeviceInfo = deviceInfo;
+
+ // make copies of the arrays
+ mInputPortBusy = new boolean[inputPortBusy.length];
+ System.arraycopy(inputPortBusy, 0, mInputPortBusy, 0, inputPortBusy.length);
+ mOutputPortOpenCount = new int[outputPortOpenCount.length];
+ System.arraycopy(outputPortOpenCount, 0, mOutputPortOpenCount, 0,
+ outputPortOpenCount.length);
+ }
+
+ /**
+ * Creates a MidiDeviceStatus with false for all input port busy values
+ * and zero for all output port open counts
+ * @hide
+ */
+ public MidiDeviceStatus(MidiDeviceInfo deviceInfo) {
+ mDeviceInfo = deviceInfo;
+ mInputPortBusy = new boolean[deviceInfo.getInputPortCount()];
+ mOutputPortOpenCount = new int[deviceInfo.getOutputPortCount()];
+ }
+
+ /**
+ * Returns the {@link MidiDeviceInfo} of the device.
+ *
+ * @return the device info
+ */
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Returns true if an input port is busy.
+ *
+ * @param input port's port number
+ * @return input port busy status
+ */
+ public boolean isInputPortBusy(int portNumber) {
+ return mInputPortBusy[portNumber];
+ }
+
+ /**
+ * Returns the open count for an output port.
+ *
+ * @param output port's port number
+ * @return output port open count
+ */
+ public int getOutputPortOpenCount(int portNumber) {
+ return mOutputPortOpenCount[portNumber];
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(mDeviceInfo.toString());
+ int inputPortCount = mDeviceInfo.getInputPortCount();
+ int outputPortCount = mDeviceInfo.getOutputPortCount();
+ builder.append(" mInputPortBusy=[");
+ for (int i = 0; i < inputPortCount; i++) {
+ builder.append(mInputPortBusy[i]);
+ if (i < inputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("] mOutputPortOpenCount=[");
+ for (int i = 0; i < outputPortCount; i++) {
+ builder.append(mOutputPortOpenCount[i]);
+ if (i < outputPortCount -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<MidiDeviceStatus> CREATOR =
+ new Parcelable.Creator<MidiDeviceStatus>() {
+ public MidiDeviceStatus createFromParcel(Parcel in) {
+ ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
+ MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+ boolean[] inputPortBusy = in.createBooleanArray();
+ int[] outputPortOpenCount = in.createIntArray();
+ return new MidiDeviceStatus(deviceInfo, inputPortBusy, outputPortOpenCount);
+ }
+
+ public MidiDeviceStatus[] newArray(int size) {
+ return new MidiDeviceStatus[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mDeviceInfo, flags);
+ parcel.writeBooleanArray(mInputPortBusy);
+ parcel.writeIntArray(mOutputPortOpenCount);
+ }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
index 90789e5..d13ca74 100644
--- a/media/java/android/media/midi/MidiDispatcher.java
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -55,11 +55,11 @@
};
/**
- * Returns whether this dispatcher contains any receivers.
- * @return true if the receiver list is not empty
+ * Returns the number of {@link MidiReceiver}s this dispatcher contains.
+ * @return the number of receivers
*/
- public boolean hasReceivers() {
- return mReceivers.size() > 0;
+ public int getReceiverCount() {
+ return mReceivers.size();
}
/**
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 74e1fa4..752075e 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -41,7 +41,8 @@
private IMidiDeviceServer mDeviceServer;
private final IBinder mToken;
private final int mPortNumber;
- private final FileOutputStream mOutputStream;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private FileOutputStream mOutputStream;
private final CloseGuard mGuard = CloseGuard.get();
private boolean mIsClosed;
@@ -53,8 +54,9 @@
ParcelFileDescriptor pfd, int portNumber) {
mDeviceServer = server;
mToken = token;
+ mParcelFileDescriptor = pfd;
mPortNumber = portNumber;
- mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+ mOutputStream = new FileOutputStream(pfd.getFileDescriptor());
mGuard.open("close");
}
@@ -89,11 +91,27 @@
}
synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
+ // used by MidiDevice.connectInputPort() to connect our socket directly to another device
+ /* package */ ParcelFileDescriptor claimFileDescriptor() {
+ synchronized (mBuffer) {
+ ParcelFileDescriptor pfd = mParcelFileDescriptor;
+ if (pfd != null) {
+ IoUtils.closeQuietly(mOutputStream);
+ mParcelFileDescriptor = null;
+ mOutputStream = null;
+ }
+ return pfd;
+ }
+ }
+
@Override
public int getMaxMessageSize() {
return MidiPortImpl.MAX_PACKET_DATA_SIZE;
@@ -104,7 +122,16 @@
synchronized (mGuard) {
if (mIsClosed) return;
mGuard.close();
- mOutputStream.close();
+ synchronized (mBuffer) {
+ if (mParcelFileDescriptor != null) {
+ mParcelFileDescriptor.close();
+ mParcelFileDescriptor = null;
+ }
+ if (mOutputStream != null) {
+ mOutputStream.close();
+ mOutputStream = null;
+ }
+ }
if (mDeviceServer != null) {
try {
mDeviceServer.closePort(mToken);
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d7b8c57..bab9064 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -62,6 +62,7 @@
mHandler = handler;
}
+ @Override
public void onDeviceAdded(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -75,6 +76,7 @@
}
}
+ @Override
public void onDeviceRemoved(MidiDeviceInfo device) {
if (mHandler != null) {
final MidiDeviceInfo deviceF = device;
@@ -87,25 +89,49 @@
mCallback.onDeviceRemoved(device);
}
}
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ if (mHandler != null) {
+ final MidiDeviceStatus statusF = status;
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mCallback.onDeviceStatusChanged(statusF);
+ }
+ });
+ } else {
+ mCallback.onDeviceStatusChanged(status);
+ }
+ }
}
/**
* Callback class used for clients to receive MIDI device added and removed notifications
*/
- abstract public static class DeviceCallback {
+ public static class DeviceCallback {
/**
* Called to notify when a new MIDI device has been added
*
* @param device a {@link MidiDeviceInfo} for the newly added device
*/
- abstract public void onDeviceAdded(MidiDeviceInfo device);
+ public void onDeviceAdded(MidiDeviceInfo device) {
+ }
/**
* Called to notify when a MIDI device has been removed
*
* @param device a {@link MidiDeviceInfo} for the removed device
*/
- abstract public void onDeviceRemoved(MidiDeviceInfo device);
+ public void onDeviceRemoved(MidiDeviceInfo device) {
+ }
+
+ /**
+ * Called to notify when the status of a MIDI device has changed
+ *
+ * @param device a {@link MidiDeviceStatus} for the changed device
+ */
+ public void onDeviceStatusChanged(MidiDeviceStatus status) {
+ }
}
/**
@@ -251,10 +277,10 @@
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
- int numOutputPorts, Bundle properties, int type) {
+ int numOutputPorts, Bundle properties, int type, MidiDeviceServer.Callback callback) {
try {
MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
- numOutputPorts);
+ numOutputPorts, callback);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
inputPortReceivers.length, numOutputPorts, properties, type);
if (deviceInfo == null) {
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
index 14575ee..b7f0fbd 100644
--- a/native/graphics/jni/Android.mk
+++ b/native/graphics/jni/Android.mk
@@ -31,7 +31,7 @@
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
# TODO: This is to work around b/19059885. Remove after root cause is fixed
-LOCAL_LDFLAGS := -Wl,--hash-style=both
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
include $(BUILD_SHARED_LIBRARY)
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 7f98b30..d534548 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -29,6 +29,7 @@
import android.media.midi.IMidiManager;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -147,6 +148,19 @@
}
}
+ public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ try {
+ for (IMidiDeviceListener listener : mListeners) {
+ listener.onDeviceStatusChanged(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
public void binderDied() {
removeClient(mToken);
}
@@ -187,6 +201,8 @@
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
+ private MidiDeviceStatus mDeviceStatus;
+ private IBinder mDeviceStatusToken;
// ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
private final ServiceInfo mServiceInfo;
// UID of device implementation
@@ -204,6 +220,33 @@
return mDeviceInfo;
}
+ public MidiDeviceStatus getDeviceStatus() {
+ return mDeviceStatus;
+ }
+
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ mDeviceStatus = status;
+
+ if (mDeviceStatusToken == null && token != null) {
+ // register a death recipient so we can clear the status when the device dies
+ try {
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ // reset to default status and clear the token
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ mDeviceStatusToken = null;
+ notifyDeviceStatusChanged(Device.this, mDeviceStatus);
+ }
+ }, 0);
+ mDeviceStatusToken = token;
+ } catch (RemoteException e) {
+ // reset to default status
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ }
+ }
+ }
+
public IMidiDeviceServer getDeviceServer() {
return mServer;
}
@@ -216,6 +259,10 @@
return (mServiceInfo == null ? null : mServiceInfo.packageName);
}
+ public int getUid() {
+ return mUid;
+ }
+
public boolean isUidAllowed(int uid) {
return (!mDeviceInfo.isPrivate() || mUid == uid);
}
@@ -302,13 +349,14 @@
@Override
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, Bundle properties, int type) {
- if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+ int uid = Binder.getCallingUid();
+ if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create non-virtual devices");
}
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, properties,
- server, null, false, -1);
+ server, null, false, uid);
}
}
@@ -337,6 +385,39 @@
}
}
+ @Override
+ public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ throw new IllegalArgumentException("no such device for " + deviceInfo);
+ }
+ return device.getDeviceStatus();
+ }
+
+ @Override
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ // Just return quietly here if device no longer exists
+ return;
+ }
+ if (Binder.getCallingUid() != device.getUid()) {
+ throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid()
+ + " does not match device's UID " + device.getUid());
+ }
+ device.setDeviceStatus(token, status);
+ notifyDeviceStatusChanged(device, status);
+ }
+
+ private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) {
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceStatusChanged(device, status);
+ }
+ }
+ }
+
// synchronize on mDevicesByInfo
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo,
@@ -469,17 +550,15 @@
continue;
}
- int uid = -1;
- if (isPrivate) {
- try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- serviceInfo.packageName, 0);
- uid = appInfo.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "could not fetch ApplicationInfo for "
- + serviceInfo.packageName);
- continue;
- }
+ int uid;
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ serviceInfo.packageName, 0);
+ uid = appInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "could not fetch ApplicationInfo for "
+ + serviceInfo.packageName);
+ continue;
}
synchronized (mDevicesByInfo) {
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index b8a3155..b99c436 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -25,7 +25,11 @@
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Build;
+import android.os.Handler;
import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
import android.view.Display;
import com.android.server.LocalServices;
@@ -37,7 +41,7 @@
private static final String TAG = "BurnInProtection";
// Default value when max burnin radius is not set.
- public static final int BURN_IN_RADIUS_MAX_DEFAULT = -1;
+ public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1;
private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
@@ -74,23 +78,19 @@
updateBurnInProtection();
}
};
-
- public BurnInProtectionHelper(Context context) {
+
+ public BurnInProtectionHelper(Context context, int minHorizontalOffset,
+ int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset,
+ int maxOffsetRadius) {
final Resources resources = context.getResources();
- mMinHorizontalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
- mMaxHorizontalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
- mMinVerticalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
- mMaxVerticalBurnInOffset = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
- int burnInRadiusMax = resources.getInteger(
- com.android.internal.R.integer.config_burnInProtectionMaxRadius);
- if (burnInRadiusMax != BURN_IN_RADIUS_MAX_DEFAULT) {
- mBurnInRadiusMaxSquared = burnInRadiusMax * burnInRadiusMax;
+ mMinHorizontalBurnInOffset = minHorizontalOffset;
+ mMaxHorizontalBurnInOffset = maxHorizontalOffset;
+ mMinVerticalBurnInOffset = minVerticalOffset;
+ mMaxVerticalBurnInOffset = maxHorizontalOffset;
+ if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) {
+ mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius;
} else {
- mBurnInRadiusMaxSquared = BURN_IN_RADIUS_MAX_DEFAULT;
+ mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT;
}
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -175,7 +175,7 @@
}
}
// If we are outside of the radius, let's try again.
- } while (mBurnInRadiusMaxSquared != BURN_IN_RADIUS_MAX_DEFAULT
+ } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT
&& mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset
> mBurnInRadiusMaxSquared);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 62e7af4..29a7fd3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -49,6 +49,7 @@
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.media.session.MediaSessionLegacyHelper;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.FactoryTest;
@@ -94,6 +95,7 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -1185,6 +1187,12 @@
}
};
+ private boolean isRoundWindow() {
+ return mContext.getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound)
+ || (Build.HARDWARE.contains("goldfish")
+ && SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false));
+ }
+
/** {@inheritDoc} */
@Override
public void init(Context context, IWindowManager windowManager,
@@ -1194,9 +1202,40 @@
mWindowManagerFuncs = windowManagerFuncs;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
- if (context.getResources().getBoolean(
- com.android.internal.R.bool.config_enableBurnInProtection)){
- mBurnInProtectionHelper = new BurnInProtectionHelper(context);
+
+ // Init display burn-in protection
+ boolean burnInProtectionEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableBurnInProtection);
+ // Allow a system property to override this. Used by developer settings.
+ boolean burnInProtectionDevMode =
+ SystemProperties.getBoolean("persist.debug.force_burn_in", false);
+ if (burnInProtectionEnabled || burnInProtectionDevMode) {
+ final int minHorizontal;
+ final int maxHorizontal;
+ final int minVertical;
+ final int maxVertical;
+ final int maxRadius;
+ if (burnInProtectionDevMode) {
+ minHorizontal = -8;
+ maxHorizontal = 8;
+ minVertical = -8;
+ maxVertical = -4;
+ maxRadius = (isRoundWindow()) ? 6 : -1;
+ } else {
+ Resources resources = context.getResources();
+ minHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
+ maxHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
+ minVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
+ maxVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
+ maxRadius = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxRadius);
+ }
+ mBurnInProtectionHelper = new BurnInProtectionHelper(
+ context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
}
mHandler = new PolicyHandler();
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ef70895..4c06cbe 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -977,7 +977,9 @@
+ " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+ " transit=" + transit + " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = createClipRevealAnimationLocked(transit, enter, appWidth, appHeight);
+ a = createClipRevealAnimationLocked(transit, enter,
+ containingFrame.right - containingFrame.left,
+ containingFrame.bottom - containingFrame.top);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 89ed5b7..ac1b0f1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1139,6 +1139,8 @@
mShownAlpha *= appTransformation.getAlpha();
if (appTransformation.hasClipRect()) {
mClipRect.set(appTransformation.getClipRect());
+ // Account for non-fullscreen windows
+ mClipRect.offset(frame.left, frame.top);
if (mWin.mHScale > 0) {
mClipRect.left /= mWin.mHScale;
mClipRect.right /= mWin.mHScale;
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index f927965..725f393 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -121,7 +121,7 @@
int outputCount = mOutputStreams.length;
mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
- properties, MidiDeviceInfo.TYPE_USB);
+ properties, MidiDeviceInfo.TYPE_USB, null);
if (mServer == null) {
return false;
}