Using workspace to dertermine decor to use

This patch uses the assigned stack to determine if
a non client decor needs to be added or not.

Since the visibility of the non client decor is
dependent on the used workspace, and an application
can transfer to a different workspace at any time,
the visibility might also change at any time.
As such this patch will also create the nc decor
for every window which might need it later in time.

BUG: 22984908
Change-Id: Ic7c0b3fd93d021bce882a5345738ceb3f608a3b9
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 807f082..eccf5fa 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.policy;
 
+import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.getMode;
@@ -163,6 +165,14 @@
     // This is the top-level view of the window, containing the window decor.
     private DecorView mDecor;
 
+    // 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;
+
     // This is the view in which the window contents are placed. It is either
     // mDecor itself, or a child of mDecor where the contents go.
     private ViewGroup mContentParent;
@@ -657,6 +667,16 @@
                 }
             }
         }
+        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.phoneWindowUpdated(hasNonClientDecor(mWorkspaceId),
+                        nonClientDecorHasShadow(mWorkspaceId));
+                mDecor.enableNonClientDecor(hasNonClientDecor(workspaceId));
+            }
+        }
     }
 
     private static void clearMenuViews(PanelFeatureState st) {
@@ -3499,24 +3519,34 @@
                 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
         }
 
-        // Set when the window is free floating and a non client decor frame was added.
-        void enableNonClientDecor(boolean enable) {
-            if (mHasNonClientDecor != enable) {
-                mHasNonClientDecor = enable;
+        /**
+         * Informs the decor if a non client decor is attached and visible.
+         * @param attachedAndVisible true when the decor is visible.
+         * Note that this will even be called if there is no non client decor.
+         **/
+        void enableNonClientDecor(boolean attachedAndVisible) {
+            if (mHasNonClientDecor != attachedAndVisible) {
+                mHasNonClientDecor = attachedAndVisible;
                 if (getForeground() != null) {
                     drawableChanged();
                 }
             }
         }
 
-        // Returns true if the window has a non client decor.
+        /**
+         * Returns true if the window has a non client decor.
+         * @return If there is a non client decor - even if it is not visible.
+         **/
         private boolean windowHasNonClientDecor() {
             return mHasNonClientDecor;
         }
 
-        // Returns true if the Window is free floating and has a shadow. Note that non overlapping
-        // windows do not have a shadow since it could not be seen anyways (a small screen / tablet
-        // "tiles" the windows side by side but does not overlap them).
+        /**
+         * Returns true if the Window is free floating and has a shadow. Note that non overlapping
+         * windows do not have a shadow since it could not be seen anyways (a small screen / tablet
+         * "tiles" the windows side by side but does not overlap them).
+         * @return Returns true when the window has a shadow created by the non client decor.
+         **/
         private boolean windowHasShadow() {
             return windowHasNonClientDecor() && getElevation() > 0;
         }
@@ -3910,12 +3940,12 @@
 
         mDecor.startChanging();
 
-        NonClientDecorView nonClientDecorView = createNonClientDecorView();
+        mNonClientDecorView = createNonClientDecorView();
         View in = mLayoutInflater.inflate(layoutResource, null);
-        if (nonClientDecorView != null) {
-            decor.addView(nonClientDecorView,
+        if (mNonClientDecorView != null) {
+            decor.addView(mNonClientDecorView,
                     new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-            nonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            mNonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         } else {
             decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
@@ -3976,32 +4006,13 @@
 
     // Free floating overlapping windows require a non client decor with a caption and shadow..
     private NonClientDecorView createNonClientDecorView() {
-        boolean needsDecor = true;
         NonClientDecorView nonClientDecorView = null;
-
         final WindowManager.LayoutParams attrs = getAttributes();
-        // TODO(skuhne): Use the associated stack to figure out if the window is on the free style
-        // desktop, the side by side desktop or the full screen desktop. With that informations the
-        // choice is fairly easy to decide.
-        // => This is only a kludge for now to suppress fullscreen windows, recents, launcher, etc..
-        boolean isFullscreen =
-                0 != ((mDecor.getWindowSystemUiVisibility() | mDecor.getSystemUiVisibility()) &
-                        (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION));
-        boolean isApplication = attrs.type != TYPE_BASE_APPLICATION &&
-                attrs.type != TYPE_APPLICATION;
-
-        // We do not show the non client decor if...
-        // - this is a floating dialog (which is not a real window, e.g. it cannot be maximized).
-        // - it is not an application (special windows have special functions, e.g text selector).
-        // - the application is full screen, drawing everything (since the decor would be out of the
-        //   screen in that case and could not be seen).
-        if (isFloating() || isFullscreen || isApplication) {
-            needsDecor = false;
-        }
-
-        if (needsDecor) {
-            // TODO(skuhne): If running in side by side mode on a device - turn off the shadow.
-            boolean windowHasShadow = true;
+        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+                attrs.type == TYPE_APPLICATION;
+        mWorkspaceId = getWorkspaceId();
+        // Only a non floating application window can get a non client decor.
+        if (!isFloating() && isApplication) {
             // Dependent on the brightness of the used title we either use the
             // dark or the light button frame.
             TypedValue value = new TypedValue();
@@ -4013,11 +4024,12 @@
                 nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate(
                         R.layout.non_client_decor_light, null);
             }
-            nonClientDecorView.setPhoneWindow(this, windowHasShadow);
+            nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId),
+                    nonClientDecorHasShadow(mWorkspaceId));
         }
-
-        // Tell the Decor if it has a non client decor.
-        mDecor.enableNonClientDecor(needsDecor);
+        // Tell the decor if it has a visible non client decor.
+        mDecor.enableNonClientDecor(nonClientDecorView != null &&
+                hasNonClientDecor(mWorkspaceId));
 
         return nonClientDecorView;
     }
@@ -5162,4 +5174,42 @@
     public void setIsStartingWindow(boolean isStartingWindow) {
         mIsStartingWindow = isStartingWindow;
     }
+
+    /**
+     * 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 = FULLSCREEN_WORKSPACE_STACK_ID;
+        WindowStackCallback callback = getWindowStackCallback();
+        if (callback != null) {
+            try {
+                workspaceId = callback.getWindowStackId();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
+            }
+        }
+        return workspaceId;
+    }
+
+    /**
+     * Determines if the window should show a non client decor for the workspace it is in.
+     * @param workspaceId The Id of the workspace which contains this window.
+     * @Return Returns true if the window should show a non client decor.
+     **/
+    private boolean hasNonClientDecor(int workspaceId) {
+        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
+    }
+
+    /**
+     * Determines if the window should show a shadow or not, dependent on the workspace.
+     * @param workspaceId The Id of the workspace which contains this window.
+     * @Return Returns true if the window should show a shadow.
+     **/
+    private boolean nonClientDecorHasShadow(int workspaceId) {
+        // TODO(skuhne): Add side by side mode here to add a decor.
+        return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
+    }
 }
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index d343783..d8626cd 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -21,12 +21,12 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.Window;
+import android.view.WindowInsets;
 import android.util.Log;
 import android.util.TypedValue;
 
-import android.view.ViewOutlineProvider;
-import android.view.WindowInsets;
-import android.view.Window;
 import com.android.internal.R;
 import com.android.internal.policy.PhoneWindow;
 
@@ -64,6 +64,7 @@
 
     private PhoneWindow mOwner = null;
     boolean mWindowHasShadow = false;
+    boolean mShowDecor = false;
 
     // The current focus state of the window for updating the window elevation.
     boolean mWindowHasFocus = true;
@@ -85,12 +86,12 @@
         super(context, attrs, defStyle);
     }
 
-    public void setPhoneWindow(PhoneWindow owner, boolean windowHasShadow) {
+    public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
         mOwner = owner;
         mWindowHasShadow = windowHasShadow;
+        mShowDecor = showDecor;
         if (mWindowHasShadow) {
-            // TODO(skuhne): Call setMaxElevation here as soon as b/22668382 got fixed.
-            updateElevation();
+            initializeElevation();
         }
         // By changing the outline provider to BOUNDS, the window can remove its
         // background without removing the shadow.
@@ -99,6 +100,19 @@
         findViewById(R.id.close_window).setOnClickListener(this);
     }
 
+    /**
+     * The phone window configuration has changed and the decor needs to be updated.
+     * @param showDecor True if the decor should be shown.
+     * @param windowHasShadow True when the window should show a shadow.
+     **/
+    public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
+        mShowDecor = showDecor;
+        if (windowHasShadow != mWindowHasShadow) {
+            mWindowHasShadow = windowHasShadow;
+            initializeElevation();
+        }
+    }
+
     @Override
     public void onClick(View view) {
         if (view.getId() == R.id.maximize_window) {
@@ -144,8 +158,9 @@
         updateElevation();
         mAllowUpdateElevation = true;
 
-        // Remove the decor temporarily if the window entered a full screen/immersive mode.
-        final int captionHeight = isFillingScreen() ? 0 : caption.getMeasuredHeight();
+        // Don't show the decor if the window has e.g. entered full screen.
+        final int captionHeight =
+                (isFillingScreen() || !mShowDecor) ? 0 : caption.getMeasuredHeight();
         caption.layout(leftPos, topPos + systemMargin, leftPos + width,
                 topPos + systemMargin + captionHeight);
 
@@ -155,27 +170,45 @@
         }
     }
 
-    // Make sure that we never get more then one client area in our view.
     @Override
     public void addView(View child, int index, LayoutParams params) {
+        // Make sure that we never get more then one client area in our view.
         if (index >= 2 || getChildCount() >= 2) {
             throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
         }
         super.addView(child, index, params);
     }
 
-    // Returns true when the window is filling the entire screen and the non client area
-    // should not be shown.
+    /**
+     * Determine if the workspace is entirely covered by the window.
+     * @return Returns true when the window is filling the entire screen/workspace.
+     **/
     private boolean isFillingScreen() {
         return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
                 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                         View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
     }
 
-    // The shadow height gets controlled by the focus to visualize highlighted windows.
-    // Note: This will overwrite application elevation properties.
-    // Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
-    //       will get no shadow as they are expected to be "full screen".
+    /**
+     * The elevation gets set for the first time and the framework needs to be informed that
+     * the surface layer gets created with the shadow size in mind.
+     **/
+    private void initializeElevation() {
+        // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
+        mAllowUpdateElevation = false;
+        if (mWindowHasShadow) {
+            updateElevation();
+        } else {
+            mOwner.setElevation(0);
+        }
+    }
+
+    /**
+     * The shadow height gets controlled by the focus to visualize highlighted windows.
+     * Note: This will overwrite application elevation properties.
+     * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
+     *       will get no shadow as they are expected to be "full screen".
+     **/
     private void updateElevation() {
         float elevation = 0;
         if (mWindowHasShadow) {
@@ -183,9 +216,8 @@
             elevation = fill ? 0 :
                     (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
                             DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
+            // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
             if (!mAllowUpdateElevation && !fill) {
-                // TODO(skuhne): Change this to setMaxElevation as soon as b/22668382 got fixed
-                // and remove this cludge.
                 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
             }
             // Convert the DP elevation into physical pixels.
@@ -197,12 +229,19 @@
         }
     }
 
+    /**
+     * Converts a DIP measure into physical pixels.
+     * @param dip The dip value.
+     * @return Returns the number of pixels.
+     */
     private float dipToPx(float dip) {
         return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
                 getResources().getDisplayMetrics());
     }
 
-    // Maximize the window by moving it to the maximize stack.
+    /**
+     * Maximize the window by moving it to the maximized workspace stack.
+     **/
     private void maximizeWindow() {
         Window.WindowStackCallback callback = mOwner.getWindowStackCallback();
         if (callback != null) {