Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 261eda26 authored by Wale Ogunwale's avatar Wale Ogunwale Committed by Android (Google) Code Review
Browse files

Merge changes Id86e9773,I682afe1b

* changes:
  Moved BackdropFrameRenderer from NonClientDecorView to its own class file
  Moved management of NonClientDecorView from PhoneWindow to DecorView
parents e122d305 0b3562db
Loading
Loading
Loading
Loading
+289 −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 com.android.internal.policy;

import com.android.internal.widget.NonClientDecorView;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.view.Choreographer;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.View;

/**
 * The thread which draws a fill in background while the app is resizing in areas where the app
 * content draw is lagging behind the resize operation.
 * 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.
 * @hide
 */
public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {

    private NonClientDecorView mNonClientDecorView;

    // 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 mFrameAndBackdropNode;

    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;

    // Whether to report when next frame is drawn or not.
    private boolean mReportNextDraw;

    public BackdropFrameRenderer(NonClientDecorView nonClientDecorView,
            ThreadedRenderer renderer,
            Rect initialBounds) {
        mNonClientDecorView = nonClientDecorView;
        setName("ResizeFrame");
        mRenderer = renderer;

        // Create a render node for the content and frame backdrop
        // which can be resized independently from the content.
        mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);

        mRenderer.addRenderNode(mFrameAndBackdropNode, true);

        // Set the initial bounds and draw once so that we do not get a broken frame.
        mTargetRect.set(initialBounds);
        synchronized (this) {
            changeWindowSizeLocked(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() {
        synchronized (this) {
            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 node again
                // (see comment above - better to do that only once).
                mRenderer.removeRenderNode(mFrameAndBackdropNode);

                mRenderer = null;

                // Exit the renderer loop.
                pingRenderLocked();
            }
        }
    }

    @Override
    public void run() {
        try {
            Looper.prepare();
            synchronized (this) {
                mChoreographer = Choreographer.getInstance();

                // Draw at least once.
                mChoreographer.postFrameCallback(this);
            }
            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) {
        synchronized (this) {
            if (mRenderer == null) {
                reportDrawIfNeeded();
                // Tell the looper to stop. We are done.
                Looper.myLooper().quit();
                return;
            }
            mNewTargetRect.set(mTargetRect);
            if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
                mOldTargetRect.set(mNewTargetRect);
                changeWindowSizeLocked(mNewTargetRect);
            }
        }
    }

    /**
     * The content is about to be drawn and we got the location of where it will be shown.
     * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
     * if the previous call was ignored since the size was unknown.
     * @param xOffset The x offset where the content is drawn to.
     * @param yOffset The y offset where the content is drawn to.
     * @param xSize The width size of the content. This should not be 0.
     * @param ySize The height of the content.
     * @return true if a frame should be requested after the content is drawn; false otherwise.
     */
    public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
        synchronized (this) {
            final boolean firstCall = mLastContentWidth == 0;
            // The current content buffer is drawn here.
            mLastContentWidth = xSize;
            mLastContentHeight = ySize - mLastCaptionHeight;
            mLastXOffset = xOffset;
            mLastYOffset = yOffset;

            mRenderer.setContentDrawBounds(
                    mLastXOffset,
                    mLastYOffset,
                    mLastXOffset + mLastContentWidth,
                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
            // If this was the first call and changeWindowSizeLocked got already called prior
            // to us, we should re-issue a changeWindowSizeLocked now.
            return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor);
        }
    }

    public void onRequestDraw(boolean reportNextDraw) {
        synchronized (this) {
            mReportNextDraw = reportNextDraw;
            mOldTargetRect.set(0, 0, 0, 0);
            pingRenderLocked();
        }
    }

    /**
     * Resizing the frame to fit the new window size.
     * @param newBounds The window bounds which needs to be drawn.
     */
    private void changeWindowSizeLocked(Rect newBounds) {
        // While a configuration change is taking place the view hierarchy might become
        // inaccessible. For that case we remember the previous metrics to avoid flashes.
        // Note that even when there is no visible caption, the caption child will exist.
        View caption = mNonClientDecorView.getChildAt(0);
        if (caption != null) {
            final int captionHeight = caption.getHeight();
            // The caption height will probably never dynamically change while we are resizing.
            // Once set to something other then 0 it should be kept that way.
            if (captionHeight != 0) {
                // Remember the height of the caption.
                mLastCaptionHeight = captionHeight;
            }
        }
        // Make sure that the other thread has already prepared the render draw calls for the
        // content. If any size is 0, we have to wait for it to be drawn first.
        if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) ||
                mLastContentWidth == 0 || mLastContentHeight == 0) {
            return;
        }
        // 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();

        mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);

        // Draw the caption and content backdrops in to our render node.
        DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
        mNonClientDecorView.mCaptionBackgroundDrawable.setBounds(
                0, 0, left + width, top + mLastCaptionHeight);
        mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas);

        // The backdrop: clear everything with the background. Clipping is done elsewhere.
        mNonClientDecorView.mResizingBackgroundDrawable.setBounds(
                0, mLastCaptionHeight, left + width, top + height);
        mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas);
        mFrameAndBackdropNode.end(canvas);

        // We need to render the node explicitly
        mRenderer.drawRenderNode(mFrameAndBackdropNode);

        reportDrawIfNeeded();
    }

    /**
     * Notify view root that a frame has been drawn by us, if it has requested so.
     */
    private void reportDrawIfNeeded() {
        if (mReportNextDraw) {
            if (mNonClientDecorView.isAttachedToWindow()) {
                mNonClientDecorView.getViewRootImpl().reportDrawFinish();
            }
            mReportNextDraw = false;
        }
    }

    /**
     * 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);
        }
    }
}
+161 −5
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.NonClientDecorView;

import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -38,6 +39,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
@@ -46,6 +48,7 @@ import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@@ -63,6 +66,8 @@ import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.PopupWindow;

import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getMode;
@@ -72,6 +77,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    private static final String TAG = "DecorView";
@@ -151,6 +158,15 @@ class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    private Rect mTempRect;
    private Rect mOutsets = new Rect();

    // This is the non client decor view for the window, containing the caption and window control
    // buttons. The visibility of this decor depends on the workspace and the window type.
    // If the window type does not require such a view, this member might be null.
    NonClientDecorView mNonClientDecorView;

    // The non client decor needs to adapt to the used workspace. Since querying and changing the
    // workspace is expensive, this is the workspace value the window is currently set up for.
    int mWorkspaceId;

    DecorView(Context context, int featureId, PhoneWindow window) {
        super(context);
        mFeatureId = featureId;
@@ -326,7 +342,7 @@ class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
        if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
            // Don't dispatch ACTION_DOWN to the non client decor if the window is
            // resizable and the event was (starting) outside the window.
            // Window resizing events should be handled by WindowManager.
@@ -1483,16 +1499,156 @@ class DecorView extends FrameLayout implements RootViewSurfaceTaker {
     * @return Returns true when the window has a shadow created by the non client decor.
     **/
    private boolean windowHasShadow() {
        return windowHasNonClientDecor() && ActivityManager.StackId
                .hasWindowShadow(mWindow.mWorkspaceId);
        return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
    }

    void setWindow(PhoneWindow phoneWindow) {
        mWindow = phoneWindow;
        Context context = getContext();
        if (context instanceof DecorContext) {
            DecorContext decorContex = (DecorContext) context;
            decorContex.setPhoneWindow(mWindow);
            DecorContext decorContext = (DecorContext) context;
            decorContext.setPhoneWindow(mWindow);
        }
    }

    void onConfigurationChanged() {
        if (mNonClientDecorView != null) {
            int workspaceId = getWorkspaceId();
            if (mWorkspaceId != workspaceId) {
                mWorkspaceId = workspaceId;
                // We might have to change the kind of surface before we do anything else.
                mNonClientDecorView.onConfigurationChanged(
                        ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
                        ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
                enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
            }
        }
    }

    View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        mNonClientDecorView = createNonClientDecorView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mNonClientDecorView != null) {
            if (mNonClientDecorView.getParent() == null) {
                addView(mNonClientDecorView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mNonClientDecorView.addView(root,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        return root;
    }

    // Free floating overlapping windows require a non client decor with a caption and shadow..
    private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
        NonClientDecorView nonClientDecorView = null;
        for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
            View view = getChildAt(i);
            if (view instanceof NonClientDecorView) {
                // The decor was most likely saved from a relaunch - so reuse it.
                nonClientDecorView = (NonClientDecorView) view;
                removeViewAt(i);
            }
        }
        final WindowManager.LayoutParams attrs = mWindow.getAttributes();
        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
                attrs.type == TYPE_APPLICATION;
        mWorkspaceId = getWorkspaceId();
        // Only a non floating application window on one of the allowed workspaces can get a non
        // client decor.
        if (!mWindow.isFloating()
                && isApplication
                && ActivityManager.StackId.isStaticStack(mWorkspaceId)) {
            // Dependent on the brightness of the used title we either use the
            // dark or the light button frame.
            if (nonClientDecorView == null) {
                Context context = getContext();
                TypedValue value = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
                inflater = inflater.from(context);
                if (Color.luminance(value.data) < 0.5) {
                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
                            R.layout.non_client_decor_dark, null);
                } else {
                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
                            R.layout.non_client_decor_light, null);
                }
            }
            nonClientDecorView.setPhoneWindow(mWindow,
                    ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
                    ActivityManager.StackId.hasWindowShadow(mWorkspaceId),
                    getResizingBackgroundDrawable(),
                    getContext().getDrawable(R.drawable.non_client_decor_title_focused));
        }
        // Tell the decor if it has a visible non client decor.
        enableNonClientDecor(
                nonClientDecorView != null && ActivityManager.StackId.hasWindowDecor(mWorkspaceId));

        return nonClientDecorView;
    }

    /**
     * Returns the color used to fill areas the app has not rendered content to yet when the user
     * is resizing the window of an activity in multi-window mode.
     * */
    private Drawable getResizingBackgroundDrawable() {
        final Context context = getContext();

        if (mWindow.mBackgroundResource != 0) {
            final Drawable drawable = context.getDrawable(mWindow.mBackgroundResource);
            if (drawable != null) {
                return drawable;
            }
        }

        if (mWindow.mBackgroundFallbackResource != 0) {
            final Drawable fallbackDrawable =
                    context.getDrawable(mWindow.mBackgroundFallbackResource);
            if (fallbackDrawable != null) {
                return fallbackDrawable;
            }
        }

        // We shouldn't really get here as the background fallback should be always available since
        // it is defaulted by the system.
        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
        return null;
    }

    /**
     * Returns the Id of the workspace which contains this window.
     * Note that if no workspace can be determined - which usually means that it was not
     * created for an activity - the fullscreen workspace ID will be returned.
     * @return Returns the workspace stack id which contains this window.
     **/
    private int getWorkspaceId() {
        int workspaceId = INVALID_STACK_ID;
        final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
        if (callback != null) {
            try {
                workspaceId = callback.getWindowStackId();
            } catch (RemoteException ex) {
                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
            }
        }
        if (workspaceId == INVALID_STACK_ID) {
            return FULLSCREEN_WORKSPACE_STACK_ID;
        }
        return workspaceId;
    }

    void clearContentView() {
        if (mNonClientDecorView != null) {
            if (mNonClientDecorView.getChildCount() > 1) {
                mNonClientDecorView.removeViewAt(1);
            }
        } else {
            // This window doesn't have non client decor, so we need to just remove the
            // children of the decor view.
            removeAllViews();
        }
    }

+11 −142

File changed.

Preview size limit exceeded, changes collapsed.

+20 −276

File changed.

Preview size limit exceeded, changes collapsed.