Loading core/java/android/view/ThreadedRenderer.java +5 −5 Original line number Diff line number Diff line Loading @@ -349,7 +349,7 @@ public class ThreadedRenderer extends HardwareRenderer { * @param right The right side of the protected bounds. * @param bottom The bottom side of the protected bounds. */ public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { public void setContentDrawBounds(int left, int top, int right, int bottom) { mStagedContentBounds.set(left, top, right, bottom); } Loading @@ -370,7 +370,7 @@ public class ThreadedRenderer extends HardwareRenderer { // renderer. if (!mCurrentContentBounds.equals(mStagedContentBounds)) { mCurrentContentBounds.set(mStagedContentBounds); nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left, nSetContentDrawBounds(mNativeProxy, mCurrentContentBounds.left, mCurrentContentBounds.top, mCurrentContentBounds.right, mCurrentContentBounds.bottom); } Loading Loading @@ -600,6 +600,6 @@ public class ThreadedRenderer extends HardwareRenderer { boolean placeFront); private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode); private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode); private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left, private static native void nSetContentDrawBounds(long nativeProxy, int left, int top, int right, int bottom); } core/java/android/view/ViewRootImpl.java +79 −10 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.WindowCallbacks; import android.widget.Scroller; import com.android.internal.R; Loading Loading @@ -114,6 +115,12 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV; /** * Set to false if we do not want to use the multi threaded renderer. Note that by disabling * this, WindowCallbacks will not fire. */ private static final boolean USE_MT_RENDERER = true; /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. Loading @@ -132,11 +139,11 @@ public final class ViewRootImpl implements ViewParent, static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; static final ArrayList<WindowCallbacks> sWindowCallbacks = new ArrayList(); static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList(); final Context mContext; final IWindowSession mWindowSession; Loading Loading @@ -417,6 +424,22 @@ public final class ViewRootImpl implements ViewParent, } } public static void addWindowCallbacks(WindowCallbacks callback) { if (USE_MT_RENDERER) { synchronized (sWindowCallbacks) { sWindowCallbacks.add(callback); } } } public static void removeWindowCallbacks(WindowCallbacks callback) { if (USE_MT_RENDERER) { synchronized (sWindowCallbacks) { sWindowCallbacks.remove(callback); } } } // FIXME for perf testing only private boolean mProfile = false; Loading Loading @@ -1378,6 +1401,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { endDragResizing(); destroyHardwareResources(); } if (viewVisibility == View.GONE) { Loading Loading @@ -1701,15 +1725,21 @@ public final class ViewRootImpl implements ViewParent, final boolean dragResizing = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0; if (mDragResizing != dragResizing) { mDragResizing = dragResizing; mFullRedrawNeeded = true; if (dragResizing) { startDragResizing(frame); } else { // We shouldn't come here, but if we come we should end the resize. endDragResizing(); } } if (!USE_MT_RENDERER) { if (dragResizing) { mCanvasOffsetX = mWinFrame.left; mCanvasOffsetY = mWinFrame.top; } else { mCanvasOffsetX = mCanvasOffsetY = 0; } } } catch (RemoteException e) { } Loading Loading @@ -6635,6 +6665,15 @@ public final class ViewRootImpl implements ViewParent, Configuration newConfig) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { // Tell all listeners that we are resizing the window so that the chrome can get // updated as fast as possible on a separate thread, if (mViewAncestor.get().mDragResizing) { synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowSizeIsChanging(frame); } } } viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, reportDraw, newConfig); } Loading Loading @@ -6803,6 +6842,36 @@ public final class ViewRootImpl implements ViewParent, return rq; } /** * Start a drag resizing which will inform all listeners that a window resize is taking place. */ private void startDragResizing(Rect initialBounds) { if (!mDragResizing) { mDragResizing = true; synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds); } } mFullRedrawNeeded = true; } } /** * End a drag resize which will inform all listeners that a window resize has ended. */ private void endDragResizing() { if (mDragResizing) { mDragResizing = false; synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowDragResizeEnd(); } } mFullRedrawNeeded = true; } } /** * Class for managing the accessibility interaction connection * based on the global accessibility state. Loading core/java/android/view/WindowCallbacks.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * 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.view; import android.graphics.Rect; /** * These callbacks are used to communicate window configuration changes while the user is performing * window changes. * @hide */ public interface WindowCallbacks { /** * Called by the system when the window got changed by the user, before the layouter got called. * It can be used to perform a "quick and dirty" resize which should never take more then 4ms to * complete. * * <p>At the time the layouting has not happened yet. * * @param newBounds The new window frame bounds. */ void onWindowSizeIsChanging(Rect newBounds); /** * Called when a drag resize starts. * @param initialBounds The initial bounds where the window will be. */ void onWindowDragResizeStart(Rect initialBounds); /** * Called when a drag resize ends. */ void onWindowDragResizeEnd(); } core/java/com/android/internal/policy/PhoneWindow.java +1 −1 Original line number Diff line number Diff line Loading @@ -2615,7 +2615,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_DOWN) { int y = (int)event.getY(); if (y >= (getHeight()-5) && !mWindow.hasChildren()) { Log.i(TAG, "Watchiing!"); Log.i(TAG, "Watching!"); mWatchingForMenu = true; } return false; Loading core/java/com/android/internal/widget/NonClientDecorView.java +295 −2 Original line number Diff line number Diff line Loading @@ -17,15 +17,23 @@ package com.android.internal.widget; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.os.Looper; import android.os.RemoteException; import android.util.AttributeSet; import android.view.Choreographer; import android.view.DisplayListCanvas; import android.view.MotionEvent; import android.view.RenderNode; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewRootImpl; import android.widget.LinearLayout; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.Window; import android.view.WindowCallbacks; import android.util.Log; import android.util.TypedValue; Loading Loading @@ -58,7 +66,7 @@ import com.android.internal.policy.PhoneWindow; * This will be mitigated once b/22527834 will be addressed. */ public class NonClientDecorView extends LinearLayout implements View.OnClickListener, View.OnTouchListener { implements View.OnClickListener, View.OnTouchListener, WindowCallbacks { private final static String TAG = "NonClientDecorView"; // The height of a window which has focus in DIP. private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; Loading @@ -67,6 +75,8 @@ public class NonClientDecorView extends LinearLayout private PhoneWindow mOwner = null; private boolean mWindowHasShadow = false; private boolean mShowDecor = false; // True when this object is listening for window size changes. private boolean mAttachedCallbacksToRootViewImpl = false; // True if the window is being dragged. private boolean mDragging = false; Loading @@ -85,6 +95,9 @@ public class NonClientDecorView extends LinearLayout // to max until the first layout command has been executed. private boolean mAllowUpdateElevation = false; // The resize frame renderer. private ResizeFrameThread mFrameRendererThread = null; public NonClientDecorView(Context context) { super(context); } Loading @@ -108,6 +121,18 @@ public class NonClientDecorView extends LinearLayout // By changing the outline provider to BOUNDS, the window can remove its // background without removing the shadow. mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS); if (!mAttachedCallbacksToRootViewImpl) { // If there is no window callback installed there was no window set before. Set it now. // Note that our ViewRootImpl object will not change. getViewRootImpl().addWindowCallbacks(this); mAttachedCallbacksToRootViewImpl = true; } else if (mFrameRendererThread != null) { // We are resizing and this call happened due to a configuration change. Tell the // renderer about it. mFrameRendererThread.onConfigurationChange(); } findViewById(R.id.maximize_window).setOnClickListener(this); findViewById(R.id.close_window).setOnClickListener(this); } Loading Loading @@ -251,7 +276,9 @@ public class NonClientDecorView extends LinearLayout **/ private void updateElevation() { float elevation = 0; if (mWindowHasShadow) { // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow // is bound to the content size and not the target size. if (mWindowHasShadow && mFrameRendererThread == null) { boolean fill = isFillingScreen(); elevation = fill ? 0 : (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : Loading Loading @@ -293,4 +320,270 @@ public class NonClientDecorView extends LinearLayout } } } @Override public void onWindowDragResizeStart(Rect initialBounds) { if (mOwner.isDestroyed()) { // If the owner's window is gone, we should not be able to come here anymore. releaseResources(); return; } if (mFrameRendererThread != null) { return; } final ThreadedRenderer renderer = (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer(); if (renderer != null) { mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new // element which owns the caption and has the elevation. updateElevation(); } } @Override public void onWindowDragResizeEnd() { releaseThreadedRenderer(); } @Override public void onWindowSizeIsChanging(Rect newBounds) { if (mFrameRendererThread != null) { mFrameRendererThread.setTargetRect(newBounds); } } /** * Release the renderer thread which is usually done when the user stops resizing. */ private void releaseThreadedRenderer() { if (mFrameRendererThread != null) { mFrameRendererThread.releaseRenderer(); mFrameRendererThread = null; // Bring the shadow back. updateElevation(); } } /** * Called when the parent window is destroyed to release all resources. Note that this will also * destroy the renderer thread. */ private void releaseResources() { releaseThreadedRenderer(); if (mAttachedCallbacksToRootViewImpl) { ViewRootImpl.removeWindowCallbacks(this); mAttachedCallbacksToRootViewImpl = false; } } /** * The thread which draws the chrome while we are resizing. * It starts with the creation and it ends once someone calls destroy(). * Any size changes can be passed by a call to setTargetRect will passed to the thread and * executed via the Choreographer. */ private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback { // This is containing the last requested size by a resize command. Note that this size might // or might not have been applied to the output already. private final Rect mTargetRect = new Rect(); // The render nodes for the multi threaded renderer. private ThreadedRenderer mRenderer; private RenderNode mFrameNode; private RenderNode mBackdropNode; private final Rect mOldTargetRect = new Rect(); private final Rect mNewTargetRect = new Rect(); private Choreographer mChoreographer; // Cached size values from the last render for the case that the view hierarchy is gone // during a configuration change. private int mLastContentWidth; private int mLastContentHeight; private int mLastCaptionHeight; private int mLastXOffset; private int mLastYOffset; ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) { mRenderer = renderer; // Create the render nodes for our frame and backdrop which can be resized independently // from the content. mFrameNode = RenderNode.create("FrameNode", null); mBackdropNode = RenderNode.create("BackdropNode", null); mRenderer.addRenderNode(mFrameNode, false); mRenderer.addRenderNode(mBackdropNode, true); // Set the initial bounds and draw once so that we do not get a broken frame. mTargetRect.set(initialBounds); changeWindowSize(initialBounds); // Kick off our draw thread. start(); } /** * Call this function asynchronously when the window size has been changed. The change will * be picked up once per frame and the frame will be re-rendered accordingly. * @param newTargetBounds The new target bounds. */ public void setTargetRect(Rect newTargetBounds) { synchronized (this) { mTargetRect.set(newTargetBounds); // Notify of a bounds change. pingRenderLocked(); } } /** * The window got replaced due to a configuration change. */ public void onConfigurationChange() { if (mRenderer != null) { // Enforce a window redraw. mOldTargetRect.set(0, 0, 0, 0); pingRenderLocked(); } } /** * All resources of the renderer will be released. This function can be called from the * the UI thread as well as the renderer thread. */ public void releaseRenderer() { synchronized (this) { if (mRenderer != null) { // Invalidate the current content bounds. mRenderer.setContentDrawBounds(0, 0, 0, 0); // Remove the render nodes again (see comment above - better to do that only once). mRenderer.removeRenderNode(mFrameNode); mRenderer.removeRenderNode(mBackdropNode); mRenderer = null; // Exit the renderer loop. pingRenderLocked(); } } } @Override public void run() { try { Looper.prepare(); mChoreographer = Choreographer.getInstance(); Looper.loop(); } finally { releaseRenderer(); } synchronized (this) { // Make sure no more messages are being sent. mChoreographer = null; } } /** * The implementation of the FrameCallback. * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} */ @Override public void doFrame(long frameTimeNanos) { if (mRenderer == null) { // Tell the looper to stop. We are done. Looper.myLooper().quit(); return; } // Prevent someone from changing this while we are copying. synchronized (this) { mNewTargetRect.set(mTargetRect); } if (!mNewTargetRect.equals(mOldTargetRect)) { mOldTargetRect.set(mNewTargetRect); changeWindowSize(mNewTargetRect); } } /** * Resizing the frame to fit the new window size. * @param newBounds The window bounds which needs to be drawn. */ private void changeWindowSize(Rect newBounds) { long startTime = System.currentTimeMillis(); // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. View caption = getChildAt(0); View content = getChildAt(1); if (content != null && caption != null) { mLastContentWidth = content.getWidth(); mLastContentHeight = content.getHeight(); mLastCaptionHeight = caption.getHeight(); // Get the draw position within our surface. int[] surfaceOrigin = new int[2]; surfaceOrigin[0] = 0; surfaceOrigin[1] = 0; // Get the shadow offsets. getLocationInSurface(surfaceOrigin); mLastXOffset = surfaceOrigin[0]; mLastYOffset = surfaceOrigin[1]; } // Since the surface is spanning the entire screen, we have to add the start offset of // the bounds to get to the surface location. final int left = mLastXOffset + newBounds.left; final int top = mLastYOffset + newBounds.top; final int width = newBounds.width(); final int height = newBounds.height(); // Produce the draw calls. // TODO(skuhne): Create a separate caption view which draws this. If the shadow should // be resized while the window resizes, this hierarchy needs to have the elevation. // That said - it is probably no good idea to draw the shadow every time since it costs // a considerable time which we should rather spend for resizing the content and it does // barely show while the entire screen is moving. mFrameNode.setLeftTopRightBottom(left, top, left + width, top + mLastCaptionHeight); DisplayListCanvas canvas = mFrameNode.start(width, height); canvas.drawColor(Color.BLACK); mFrameNode.end(canvas); mBackdropNode.setLeftTopRightBottom(left, top + mLastCaptionHeight, left + width, top + height); // The backdrop: clear everything with the background. Clipping is done elsewhere. canvas = mBackdropNode.start(width, height - mLastCaptionHeight); // TODO(skuhne): mOwner.getDecorView().mBackgroundFallback.draw(..) - or similar. // Note: This might not work (calculator for example uses a transparent background). canvas.drawColor(0xff808080); mBackdropNode.end(canvas); // The current content buffer is drawn here. mRenderer.setContentDrawBounds( mLastXOffset, mLastYOffset + mLastCaptionHeight, mLastXOffset + mLastContentWidth, mLastYOffset + mLastCaptionHeight + mLastContentHeight); // We need to render both rendered nodes explicitly. mRenderer.drawRenderNode(mFrameNode); mRenderer.drawRenderNode(mBackdropNode); } /** * Sends a message to the renderer to wake up and perform the next action which can be * either the next rendering or the self destruction if mRenderer is null. * Note: This call must be synchronized. */ private void pingRenderLocked() { if (mChoreographer != null) { mChoreographer.postFrameCallback(this); } } } } Loading
core/java/android/view/ThreadedRenderer.java +5 −5 Original line number Diff line number Diff line Loading @@ -349,7 +349,7 @@ public class ThreadedRenderer extends HardwareRenderer { * @param right The right side of the protected bounds. * @param bottom The bottom side of the protected bounds. */ public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { public void setContentDrawBounds(int left, int top, int right, int bottom) { mStagedContentBounds.set(left, top, right, bottom); } Loading @@ -370,7 +370,7 @@ public class ThreadedRenderer extends HardwareRenderer { // renderer. if (!mCurrentContentBounds.equals(mStagedContentBounds)) { mCurrentContentBounds.set(mStagedContentBounds); nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left, nSetContentDrawBounds(mNativeProxy, mCurrentContentBounds.left, mCurrentContentBounds.top, mCurrentContentBounds.right, mCurrentContentBounds.bottom); } Loading Loading @@ -600,6 +600,6 @@ public class ThreadedRenderer extends HardwareRenderer { boolean placeFront); private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode); private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode); private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left, private static native void nSetContentDrawBounds(long nativeProxy, int left, int top, int right, int bottom); }
core/java/android/view/ViewRootImpl.java +79 −10 Original line number Diff line number Diff line Loading @@ -73,6 +73,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.WindowCallbacks; import android.widget.Scroller; import com.android.internal.R; Loading Loading @@ -114,6 +115,12 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV; /** * Set to false if we do not want to use the multi threaded renderer. Note that by disabling * this, WindowCallbacks will not fire. */ private static final boolean USE_MT_RENDERER = true; /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. Loading @@ -132,11 +139,11 @@ public final class ViewRootImpl implements ViewParent, static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; static final ArrayList<WindowCallbacks> sWindowCallbacks = new ArrayList(); static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList(); final Context mContext; final IWindowSession mWindowSession; Loading Loading @@ -417,6 +424,22 @@ public final class ViewRootImpl implements ViewParent, } } public static void addWindowCallbacks(WindowCallbacks callback) { if (USE_MT_RENDERER) { synchronized (sWindowCallbacks) { sWindowCallbacks.add(callback); } } } public static void removeWindowCallbacks(WindowCallbacks callback) { if (USE_MT_RENDERER) { synchronized (sWindowCallbacks) { sWindowCallbacks.remove(callback); } } } // FIXME for perf testing only private boolean mProfile = false; Loading Loading @@ -1378,6 +1401,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { endDragResizing(); destroyHardwareResources(); } if (viewVisibility == View.GONE) { Loading Loading @@ -1701,15 +1725,21 @@ public final class ViewRootImpl implements ViewParent, final boolean dragResizing = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0; if (mDragResizing != dragResizing) { mDragResizing = dragResizing; mFullRedrawNeeded = true; if (dragResizing) { startDragResizing(frame); } else { // We shouldn't come here, but if we come we should end the resize. endDragResizing(); } } if (!USE_MT_RENDERER) { if (dragResizing) { mCanvasOffsetX = mWinFrame.left; mCanvasOffsetY = mWinFrame.top; } else { mCanvasOffsetX = mCanvasOffsetY = 0; } } } catch (RemoteException e) { } Loading Loading @@ -6635,6 +6665,15 @@ public final class ViewRootImpl implements ViewParent, Configuration newConfig) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { // Tell all listeners that we are resizing the window so that the chrome can get // updated as fast as possible on a separate thread, if (mViewAncestor.get().mDragResizing) { synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowSizeIsChanging(frame); } } } viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, reportDraw, newConfig); } Loading Loading @@ -6803,6 +6842,36 @@ public final class ViewRootImpl implements ViewParent, return rq; } /** * Start a drag resizing which will inform all listeners that a window resize is taking place. */ private void startDragResizing(Rect initialBounds) { if (!mDragResizing) { mDragResizing = true; synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds); } } mFullRedrawNeeded = true; } } /** * End a drag resize which will inform all listeners that a window resize has ended. */ private void endDragResizing() { if (mDragResizing) { mDragResizing = false; synchronized (sWindowCallbacks) { for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { sWindowCallbacks.get(i).onWindowDragResizeEnd(); } } mFullRedrawNeeded = true; } } /** * Class for managing the accessibility interaction connection * based on the global accessibility state. Loading
core/java/android/view/WindowCallbacks.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * 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.view; import android.graphics.Rect; /** * These callbacks are used to communicate window configuration changes while the user is performing * window changes. * @hide */ public interface WindowCallbacks { /** * Called by the system when the window got changed by the user, before the layouter got called. * It can be used to perform a "quick and dirty" resize which should never take more then 4ms to * complete. * * <p>At the time the layouting has not happened yet. * * @param newBounds The new window frame bounds. */ void onWindowSizeIsChanging(Rect newBounds); /** * Called when a drag resize starts. * @param initialBounds The initial bounds where the window will be. */ void onWindowDragResizeStart(Rect initialBounds); /** * Called when a drag resize ends. */ void onWindowDragResizeEnd(); }
core/java/com/android/internal/policy/PhoneWindow.java +1 −1 Original line number Diff line number Diff line Loading @@ -2615,7 +2615,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_DOWN) { int y = (int)event.getY(); if (y >= (getHeight()-5) && !mWindow.hasChildren()) { Log.i(TAG, "Watchiing!"); Log.i(TAG, "Watching!"); mWatchingForMenu = true; } return false; Loading
core/java/com/android/internal/widget/NonClientDecorView.java +295 −2 Original line number Diff line number Diff line Loading @@ -17,15 +17,23 @@ package com.android.internal.widget; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.os.Looper; import android.os.RemoteException; import android.util.AttributeSet; import android.view.Choreographer; import android.view.DisplayListCanvas; import android.view.MotionEvent; import android.view.RenderNode; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewRootImpl; import android.widget.LinearLayout; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.Window; import android.view.WindowCallbacks; import android.util.Log; import android.util.TypedValue; Loading Loading @@ -58,7 +66,7 @@ import com.android.internal.policy.PhoneWindow; * This will be mitigated once b/22527834 will be addressed. */ public class NonClientDecorView extends LinearLayout implements View.OnClickListener, View.OnTouchListener { implements View.OnClickListener, View.OnTouchListener, WindowCallbacks { private final static String TAG = "NonClientDecorView"; // The height of a window which has focus in DIP. private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; Loading @@ -67,6 +75,8 @@ public class NonClientDecorView extends LinearLayout private PhoneWindow mOwner = null; private boolean mWindowHasShadow = false; private boolean mShowDecor = false; // True when this object is listening for window size changes. private boolean mAttachedCallbacksToRootViewImpl = false; // True if the window is being dragged. private boolean mDragging = false; Loading @@ -85,6 +95,9 @@ public class NonClientDecorView extends LinearLayout // to max until the first layout command has been executed. private boolean mAllowUpdateElevation = false; // The resize frame renderer. private ResizeFrameThread mFrameRendererThread = null; public NonClientDecorView(Context context) { super(context); } Loading @@ -108,6 +121,18 @@ public class NonClientDecorView extends LinearLayout // By changing the outline provider to BOUNDS, the window can remove its // background without removing the shadow. mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS); if (!mAttachedCallbacksToRootViewImpl) { // If there is no window callback installed there was no window set before. Set it now. // Note that our ViewRootImpl object will not change. getViewRootImpl().addWindowCallbacks(this); mAttachedCallbacksToRootViewImpl = true; } else if (mFrameRendererThread != null) { // We are resizing and this call happened due to a configuration change. Tell the // renderer about it. mFrameRendererThread.onConfigurationChange(); } findViewById(R.id.maximize_window).setOnClickListener(this); findViewById(R.id.close_window).setOnClickListener(this); } Loading Loading @@ -251,7 +276,9 @@ public class NonClientDecorView extends LinearLayout **/ private void updateElevation() { float elevation = 0; if (mWindowHasShadow) { // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow // is bound to the content size and not the target size. if (mWindowHasShadow && mFrameRendererThread == null) { boolean fill = isFillingScreen(); elevation = fill ? 0 : (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : Loading Loading @@ -293,4 +320,270 @@ public class NonClientDecorView extends LinearLayout } } } @Override public void onWindowDragResizeStart(Rect initialBounds) { if (mOwner.isDestroyed()) { // If the owner's window is gone, we should not be able to come here anymore. releaseResources(); return; } if (mFrameRendererThread != null) { return; } final ThreadedRenderer renderer = (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer(); if (renderer != null) { mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new // element which owns the caption and has the elevation. updateElevation(); } } @Override public void onWindowDragResizeEnd() { releaseThreadedRenderer(); } @Override public void onWindowSizeIsChanging(Rect newBounds) { if (mFrameRendererThread != null) { mFrameRendererThread.setTargetRect(newBounds); } } /** * Release the renderer thread which is usually done when the user stops resizing. */ private void releaseThreadedRenderer() { if (mFrameRendererThread != null) { mFrameRendererThread.releaseRenderer(); mFrameRendererThread = null; // Bring the shadow back. updateElevation(); } } /** * Called when the parent window is destroyed to release all resources. Note that this will also * destroy the renderer thread. */ private void releaseResources() { releaseThreadedRenderer(); if (mAttachedCallbacksToRootViewImpl) { ViewRootImpl.removeWindowCallbacks(this); mAttachedCallbacksToRootViewImpl = false; } } /** * The thread which draws the chrome while we are resizing. * It starts with the creation and it ends once someone calls destroy(). * Any size changes can be passed by a call to setTargetRect will passed to the thread and * executed via the Choreographer. */ private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback { // This is containing the last requested size by a resize command. Note that this size might // or might not have been applied to the output already. private final Rect mTargetRect = new Rect(); // The render nodes for the multi threaded renderer. private ThreadedRenderer mRenderer; private RenderNode mFrameNode; private RenderNode mBackdropNode; private final Rect mOldTargetRect = new Rect(); private final Rect mNewTargetRect = new Rect(); private Choreographer mChoreographer; // Cached size values from the last render for the case that the view hierarchy is gone // during a configuration change. private int mLastContentWidth; private int mLastContentHeight; private int mLastCaptionHeight; private int mLastXOffset; private int mLastYOffset; ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) { mRenderer = renderer; // Create the render nodes for our frame and backdrop which can be resized independently // from the content. mFrameNode = RenderNode.create("FrameNode", null); mBackdropNode = RenderNode.create("BackdropNode", null); mRenderer.addRenderNode(mFrameNode, false); mRenderer.addRenderNode(mBackdropNode, true); // Set the initial bounds and draw once so that we do not get a broken frame. mTargetRect.set(initialBounds); changeWindowSize(initialBounds); // Kick off our draw thread. start(); } /** * Call this function asynchronously when the window size has been changed. The change will * be picked up once per frame and the frame will be re-rendered accordingly. * @param newTargetBounds The new target bounds. */ public void setTargetRect(Rect newTargetBounds) { synchronized (this) { mTargetRect.set(newTargetBounds); // Notify of a bounds change. pingRenderLocked(); } } /** * The window got replaced due to a configuration change. */ public void onConfigurationChange() { if (mRenderer != null) { // Enforce a window redraw. mOldTargetRect.set(0, 0, 0, 0); pingRenderLocked(); } } /** * All resources of the renderer will be released. This function can be called from the * the UI thread as well as the renderer thread. */ public void releaseRenderer() { synchronized (this) { if (mRenderer != null) { // Invalidate the current content bounds. mRenderer.setContentDrawBounds(0, 0, 0, 0); // Remove the render nodes again (see comment above - better to do that only once). mRenderer.removeRenderNode(mFrameNode); mRenderer.removeRenderNode(mBackdropNode); mRenderer = null; // Exit the renderer loop. pingRenderLocked(); } } } @Override public void run() { try { Looper.prepare(); mChoreographer = Choreographer.getInstance(); Looper.loop(); } finally { releaseRenderer(); } synchronized (this) { // Make sure no more messages are being sent. mChoreographer = null; } } /** * The implementation of the FrameCallback. * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} */ @Override public void doFrame(long frameTimeNanos) { if (mRenderer == null) { // Tell the looper to stop. We are done. Looper.myLooper().quit(); return; } // Prevent someone from changing this while we are copying. synchronized (this) { mNewTargetRect.set(mTargetRect); } if (!mNewTargetRect.equals(mOldTargetRect)) { mOldTargetRect.set(mNewTargetRect); changeWindowSize(mNewTargetRect); } } /** * Resizing the frame to fit the new window size. * @param newBounds The window bounds which needs to be drawn. */ private void changeWindowSize(Rect newBounds) { long startTime = System.currentTimeMillis(); // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. View caption = getChildAt(0); View content = getChildAt(1); if (content != null && caption != null) { mLastContentWidth = content.getWidth(); mLastContentHeight = content.getHeight(); mLastCaptionHeight = caption.getHeight(); // Get the draw position within our surface. int[] surfaceOrigin = new int[2]; surfaceOrigin[0] = 0; surfaceOrigin[1] = 0; // Get the shadow offsets. getLocationInSurface(surfaceOrigin); mLastXOffset = surfaceOrigin[0]; mLastYOffset = surfaceOrigin[1]; } // Since the surface is spanning the entire screen, we have to add the start offset of // the bounds to get to the surface location. final int left = mLastXOffset + newBounds.left; final int top = mLastYOffset + newBounds.top; final int width = newBounds.width(); final int height = newBounds.height(); // Produce the draw calls. // TODO(skuhne): Create a separate caption view which draws this. If the shadow should // be resized while the window resizes, this hierarchy needs to have the elevation. // That said - it is probably no good idea to draw the shadow every time since it costs // a considerable time which we should rather spend for resizing the content and it does // barely show while the entire screen is moving. mFrameNode.setLeftTopRightBottom(left, top, left + width, top + mLastCaptionHeight); DisplayListCanvas canvas = mFrameNode.start(width, height); canvas.drawColor(Color.BLACK); mFrameNode.end(canvas); mBackdropNode.setLeftTopRightBottom(left, top + mLastCaptionHeight, left + width, top + height); // The backdrop: clear everything with the background. Clipping is done elsewhere. canvas = mBackdropNode.start(width, height - mLastCaptionHeight); // TODO(skuhne): mOwner.getDecorView().mBackgroundFallback.draw(..) - or similar. // Note: This might not work (calculator for example uses a transparent background). canvas.drawColor(0xff808080); mBackdropNode.end(canvas); // The current content buffer is drawn here. mRenderer.setContentDrawBounds( mLastXOffset, mLastYOffset + mLastCaptionHeight, mLastXOffset + mLastContentWidth, mLastYOffset + mLastCaptionHeight + mLastContentHeight); // We need to render both rendered nodes explicitly. mRenderer.drawRenderNode(mFrameNode); mRenderer.drawRenderNode(mBackdropNode); } /** * Sends a message to the renderer to wake up and perform the next action which can be * either the next rendering or the self destruction if mRenderer is null. * Note: This call must be synchronized. */ private void pingRenderLocked() { if (mChoreographer != null) { mChoreographer.postFrameCallback(this); } } } }