diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 807f0826822be..eccf5fa86fe93 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } } + 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { .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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 class PhoneWindow extends Window implements MenuBuilder.Callback { 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 d343783f5a5d4..d8626cd34ff11 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.os.RemoteException; 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene } } - // 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene 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 @@ public class NonClientDecorView extends ViewGroup implements View.OnClickListene } } + /** + * 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) {