diff options
47 files changed, 1018 insertions, 228 deletions
diff --git a/api/current.txt b/api/current.txt index 11db5c30aca7..c43307e31bdf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -413,6 +413,7 @@ package android { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 da5986f50249..6acea9e4549f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -485,6 +485,7 @@ package android { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 @@ package android.webkit { 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 967e80c4d7b8..0d35f9c1567a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -540,7 +540,7 @@ public class StaticLayout extends Layout { 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 69b4c47daad5..51fefe90f1aa 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -102,7 +102,6 @@ public class ThreadedRenderer extends HardwareRenderer { 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 @@ public class ThreadedRenderer extends HardwareRenderer { (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 @@ public class ThreadedRenderer extends HardwareRenderer { AtlasInitializer.sInstance.init(context, mNativeProxy); - // Setup timing - mChoreographer = Choreographer.getInstance(); - nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos()); - loadSystemProperties(); } @@ -224,7 +218,7 @@ public class ThreadedRenderer extends HardwareRenderer { mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight); nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius, - mAmbientShadowAlpha, mSpotShadowAlpha, mDensity); + mAmbientShadowAlpha, mSpotShadowAlpha); } @Override @@ -379,6 +373,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void setName(String name) { + nSetName(mNativeProxy, name); } @Override @@ -487,15 +482,15 @@ public class ThreadedRenderer extends HardwareRenderer { 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 000000000000..7683a40d114a --- /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 000000000000..eab27bdef434 --- /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 01a506c8b4da..67ad642cf02d 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -27,6 +27,7 @@ import android.graphics.Picture; 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 @@ public class WebView extends AbsoluteLayout } /** + * 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 379a7320292f..0cdb8755ef1d 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -25,6 +25,7 @@ import android.graphics.Picture; 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 interface WebViewProvider { 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 399f4c5a65ca..f67625457708 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -29,6 +29,8 @@ import android.os.Build; 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.MotionEvent; 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 @@ public class PopupWindow { 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 @@ public class PopupWindow { + "calling setContentView() before attempting to show the popup."); } + // 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(); + } + // When a background is available, we embed the content view within // another view that owns the background drawable. + final View backgroundView; if (mBackground != null) { - mBackgroundView = createBackgroundView(mContentView); - mBackgroundView.setBackground(mBackground); + backgroundView = createBackgroundView(mContentView); + backgroundView.setBackground(mBackground); } else { - mBackgroundView = mContentView; + backgroundView = mContentView; } - mDecorView = createDecorView(mBackgroundView); + 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 @@ public class PopupWindow { p.packageName = mContext.getPackageName(); } - final View rootView = mContentView.getRootView(); - rootView.setFitsSystemWindows(mLayoutInsetDecor); - setLayoutDirectionFromAnchor(); - - mWindowManager.addView(rootView, p); + final PopupDecorView decorView = mDecorView; + decorView.setFitsSystemWindows(mLayoutInsetDecor); + decorView.requestEnterTransition(mEnterTransition); - // 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); - } - }); + setLayoutDirectionFromAnchor(); - mDecorView.getViewTreeObserver().addOnGlobalLayoutListener( - new PostLayoutTransitionListener(mDecorView, mEnterTransition)); - } + mWindowManager.addView(decorView, p); } private void setLayoutDirectionFromAnchor() { @@ -1591,35 +1586,38 @@ public class PopupWindow { * @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 @@ public class PopupWindow { * 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 @@ public class PopupWindow { 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 @@ public class PopupWindow { 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 3592687d1f10..e7031fe51061 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -727,14 +727,10 @@ public class SpellChecker implements SpellCheckerSessionListener { } } - 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 2b20b386058a..7d4507150f8e 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 @@ import java.util.ArrayList; 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 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On 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 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On } } + @Override public void onDismiss() { mPopup = null; mMenu.close(); @@ -190,6 +200,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On 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 39064ed22604..fff860474c55 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -489,7 +489,7 @@ static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfaceP 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 ad933010c3e9..3d9a9eddd907 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -239,18 +239,20 @@ static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz 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 @@ static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlon 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 @@ static JNINativeMethod gMethods[] = { { "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 30ce271af38f..15797ddb81f9 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 16f06767a9c6..da911b2535c1 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 4ba6c0b9ae3b..9e87b4d9b2ac 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -45,6 +45,7 @@ please see themes_device_defaults.xml. <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 c30b3d5f60e0..701d0ef30ec7 100644 --- a/core/res/res/values/themes_holo.xml +++ b/core/res/res/values/themes_holo.xml @@ -65,6 +65,7 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 a610d07d035d..38cfecde00d7 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -45,6 +45,7 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <!-- 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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <!-- 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 46b094556ad7..7df61f272c44 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -97,8 +97,8 @@ void JankTracker::addFrame(const FrameInfo& frame) { 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::dump(int fd) { } 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 @@ void JankTracker::reset() { 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 3d4929b215c6..ae339ecf746c 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 @@ public: 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 fcf6eb2ef537..945607345191 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -51,6 +51,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , 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 9a60dc7cbbd1..c3904c2cf63f 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 @@ public: 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 @@ private: 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 f48ee414871f..35391b27cc96 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -34,7 +34,6 @@ namespace renderthread { DrawFrameTask::DrawFrameTask() : mRenderThread(nullptr) , mContext(nullptr) - , mDensity(1.0f) // safe enough default , mSyncResult(kSync_OK) { } @@ -84,7 +83,6 @@ void DrawFrameTask::postAndWait() { 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 0e56bea83169..8039643623b6 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -62,7 +62,6 @@ public: 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 @@ private: /********************************************* * 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 0fa2f23b9752..ea4216c1c986 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -97,18 +97,6 @@ void RenderProxy::destroyContext() { } } -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 @@ bool RenderProxy::loadSystemProperties() { 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 @@ CREATE_BRIDGE7(setup, CanvasContext* context, int width, int height, } 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 19e73e5be128..43cbe07cf99f 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -62,16 +62,16 @@ public: 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 2a8baa747c5d..3ac2976f97c6 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 @@ RenderThread::~RenderThread() { 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::initializeDisplayEventReceiver() { } 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 f1694243b528..80960999ef53 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 @@ public: // 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 @@ private: 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 0d1e63ee7a62..805989b24f89 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -85,7 +85,7 @@ public: 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 17d9bfd9573c..31c66e3f8250 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 3331aae649bf..642078aa669a 100644 --- a/media/java/android/media/midi/IMidiDeviceServer.aidl +++ b/media/java/android/media/midi/IMidiDeviceServer.aidl @@ -24,4 +24,7 @@ interface IMidiDeviceServer 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 617b03e70448..a3b40d68b73e 100644 --- a/media/java/android/media/midi/IMidiManager.aidl +++ b/media/java/android/media/midi/IMidiManager.aidl @@ -19,6 +19,7 @@ package android.media.midi; 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 @@ interface IMidiManager // 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 af0737da516f..569f7c645768 100644 --- a/media/java/android/media/midi/MidiDevice.java +++ b/media/java/android/media/midi/MidiDevice.java @@ -26,6 +26,8 @@ import android.util.Log; import dalvik.system.CloseGuard; +import libcore.io.IoUtils; + import java.io.Closeable; import java.io.IOException; @@ -44,8 +46,29 @@ public final class MidiDevice implements Closeable { 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 @@ public final class MidiDevice implements Closeable { } } + /** + * 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 3b4b6f0f0283..b3c0e3a48596 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 @@ public final class MidiDeviceServer implements Closeable { 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 @@ public final class MidiDeviceServer implements Closeable { 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 @@ public final class MidiDeviceServer implements Closeable { @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 @@ public final class MidiDeviceServer implements Closeable { 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 @@ public final class MidiDeviceServer implements Closeable { 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 @@ public final class MidiDeviceServer implements Closeable { } } } + + @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 @@ public final class MidiDeviceServer implements Closeable { mOutputPortDispatchers[i] = new MidiDispatcher(); } + mInputPortBusy = new boolean[mInputPortCount]; + mOutputPortOpenCount = new int[numOutputPorts]; + mGuard.open("close"); } @@ -230,9 +284,28 @@ public final class MidiDeviceServer implements Closeable { 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 @@ public final class MidiDeviceServer implements Closeable { } 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 64f69cdcd132..5f55ae2963c1 100644 --- a/media/java/android/media/midi/MidiDeviceService.java +++ b/media/java/android/media/midi/MidiDeviceService.java @@ -57,6 +57,13 @@ abstract public class MidiDeviceService extends Service { 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 @@ abstract public class MidiDeviceService extends Service { 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 @@ abstract public class MidiDeviceService extends Service { 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 000000000000..1a848c0686ad --- /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 000000000000..cc048896d0a7 --- /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 90789e583f45..d13ca74db370 100644 --- a/media/java/android/media/midi/MidiDispatcher.java +++ b/media/java/android/media/midi/MidiDispatcher.java @@ -55,11 +55,11 @@ public final class MidiDispatcher extends MidiReceiver { }; /** - * 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 74e1fa41cd8a..752075e1301c 100644 --- a/media/java/android/media/midi/MidiInputPort.java +++ b/media/java/android/media/midi/MidiInputPort.java @@ -41,7 +41,8 @@ public final class MidiInputPort extends MidiReceiver implements Closeable { 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 @@ public final class MidiInputPort extends MidiReceiver implements Closeable { 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 @@ public final class MidiInputPort extends MidiReceiver implements Closeable { } 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 @@ public final class MidiInputPort extends MidiReceiver implements Closeable { 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 d7b8c5702bd8..bab906419e25 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -62,6 +62,7 @@ public final class MidiManager { mHandler = handler; } + @Override public void onDeviceAdded(MidiDeviceInfo device) { if (mHandler != null) { final MidiDeviceInfo deviceF = device; @@ -75,6 +76,7 @@ public final class MidiManager { } } + @Override public void onDeviceRemoved(MidiDeviceInfo device) { if (mHandler != null) { final MidiDeviceInfo deviceF = device; @@ -87,25 +89,49 @@ public final class MidiManager { 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 @@ public final class MidiManager { /** @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 14575ee13eb7..b7f0fbd0317e 100644 --- a/native/graphics/jni/Android.mk +++ b/native/graphics/jni/Android.mk @@ -31,7 +31,7 @@ LOCAL_MODULE:= libjnigraphics 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 7f98b300057e..d534548d1dbc 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.IMidiDeviceServer; 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 class MidiService extends IMidiManager.Stub { } } + 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 @@ public class MidiService extends IMidiManager.Stub { 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 @@ public class MidiService extends IMidiManager.Stub { 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 @@ public class MidiService extends IMidiManager.Stub { 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 @@ public class MidiService extends IMidiManager.Stub { @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 @@ public class MidiService extends IMidiManager.Stub { } } + @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 @@ public class MidiService extends IMidiManager.Stub { 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 b8a31551ae5d..b99c436d3407 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.IntentFilter; 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 @@ public class BurnInProtectionHelper implements DisplayManager.DisplayListener { 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 @@ public class BurnInProtectionHelper implements DisplayManager.DisplayListener { 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 @@ public class BurnInProtectionHelper implements DisplayManager.DisplayListener { } } // 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 62e7af4f51be..29a7fd3f9940 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.IAudioService; 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.PhoneWindow; 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; + 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 ef7089563177..4c06cbe326ac 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 @@ public class AppTransition implements Dump { + " 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 89ed5b7ae5ff..ac1b0f1cbf23 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 @@ class WindowStateAnimator { 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 f927965af886..725f39321eed 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 @@ public final class UsbMidiDevice implements Closeable { int outputCount = mOutputStreams.length; mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount, - properties, MidiDeviceInfo.TYPE_USB); + properties, MidiDeviceInfo.TYPE_USB, null); if (mServer == null) { return false; } |