Merge changes I6b641d6b,Id573a309
* changes:
Renamed NonClientDecorView to DecorCaptionView
Control display of shadows for multi-window in DecorView
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 077cebc..4c221fc5 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -25,8 +25,8 @@
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorCaptionView;
import com.android.internal.widget.FloatingToolbar;
-import com.android.internal.widget.NonClientDecorView;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -88,6 +88,18 @@
private static final boolean SWEEP_OPEN_MENU = false;
+ // The height of a window which has focus in DIP.
+ private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
+ // The height of a window which has not in DIP.
+ private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
+
+ // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
+ // size calculation takes the shadow size into account. We set the elevation currently
+ // to max until the first layout command has been executed.
+ private boolean mAllowUpdateElevation = false;
+
+ private boolean mElevationAdjustedForStack = false;
+
int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
@@ -101,8 +113,7 @@
private final Rect mFrameOffsets = new Rect();
- // True if a non client area decor exists.
- private boolean mHasNonClientDecor = false;
+ private boolean mHasCaption = false;
private boolean mChanging;
@@ -161,18 +172,18 @@
private Rect mTempRect;
private Rect mOutsets = new Rect();
- // This is the non client decor view for the window, containing the caption and window control
+ // This is the caption 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;
+ DecorCaptionView mDecorCaptionView;
- // 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;
+ // Stack window is currently in. Since querying and changing the stack is expensive,
+ // this is the stack value the window is currently set up for.
+ int mStackId;
private boolean mWindowResizeCallbacksAdded = false;
- public BackdropFrameRenderer mBackdropFrameRenderer = null;
+ BackdropFrameRenderer mBackdropFrameRenderer = null;
private Drawable mResizingBackgroundDrawable;
private Drawable mCaptionBackgroundDrawable;
@@ -191,7 +202,7 @@
setWindow(window);
}
- public void setBackgroundFallback(int resId) {
+ void setBackgroundFallback(int resId) {
mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
}
@@ -351,10 +362,10 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
- 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.
+ if (mHasCaption && isShowingCaption()) {
+ // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
+ // was (starting) outside the window. Window resizing events should be handled by
+ // WindowManager.
// TODO: Investigate how to handle the outside touch in window manager
// without generating these events.
// Currently we receive these because we need to enlarge the window's
@@ -630,6 +641,11 @@
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
+
+ // If the application changed its SystemUI metrics, we might also have to adapt
+ // our shadow elevation.
+ updateElevation();
+ mAllowUpdateElevation = true;
}
@Override
@@ -781,11 +797,11 @@
}
}
- public void startChanging() {
+ void startChanging() {
mChanging = true;
}
- public void finishChanging() {
+ void finishChanging() {
mChanging = false;
drawableChanged();
}
@@ -1138,7 +1154,7 @@
invalidate();
int opacity = PixelFormat.OPAQUE;
- if (windowHasShadow()) {
+ if (ActivityManager.StackId.hasWindowShadow(mStackId)) {
// If the window has a shadow, it must be translucent.
opacity = PixelFormat.TRANSLUCENT;
} else{
@@ -1213,6 +1229,8 @@
if (mFloatingActionMode != null) {
mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
}
+
+ updateElevation();
}
@Override
@@ -1495,38 +1513,19 @@
}
/**
- * Informs the decor if a non client decor is attached and visible.
+ * Informs the decor if the caption 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.
+ * Note that this will even be called if there is no caption.
**/
- void enableNonClientDecor(boolean attachedAndVisible) {
- if (mHasNonClientDecor != attachedAndVisible) {
- mHasNonClientDecor = attachedAndVisible;
+ void enableCaption(boolean attachedAndVisible) {
+ if (mHasCaption != attachedAndVisible) {
+ mHasCaption = attachedAndVisible;
if (getForeground() != null) {
drawableChanged();
}
}
}
- /**
- * 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 (although at some times
- * it might not be displaying it, e.g. during a resize). 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() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
- }
-
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
Context context = getContext();
@@ -1537,91 +1536,89 @@
}
void onConfigurationChanged() {
- if (mNonClientDecorView != null) {
- int workspaceId = getWorkspaceId();
- if (mWorkspaceId != workspaceId) {
- mWorkspaceId = workspaceId;
+ int workspaceId = getStackId();
+ if (mDecorCaptionView != null) {
+ if (mStackId != workspaceId) {
+ mStackId = 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));
+ mDecorCaptionView.onConfigurationChanged(
+ ActivityManager.StackId.hasWindowDecor(mStackId));
+ enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId));
}
}
+ initializeElevation();
}
View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
- mWorkspaceId = getWorkspaceId();
+ mStackId = getStackId();
mResizingBackgroundDrawable = getResizingBackgroundDrawable(
mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
mCaptionBackgroundDrawable =
- getContext().getDrawable(R.drawable.non_client_decor_title_focused);
+ getContext().getDrawable(R.drawable.decor_caption_title_focused);
if (mBackdropFrameRenderer != null) {
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable);
}
- mNonClientDecorView = createNonClientDecorView(inflater);
+ mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getParent() == null) {
- addView(mNonClientDecorView,
+ if (mDecorCaptionView != null) {
+ if (mDecorCaptionView.getParent() == null) {
+ addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
- mNonClientDecorView.addView(root,
+ mDecorCaptionView.addView(root,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
+ initializeElevation();
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--) {
+ // Free floating overlapping windows require a caption.
+ private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
+ DecorCaptionView DecorCaptionView = null;
+ for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) {
View view = getChildAt(i);
- if (view instanceof NonClientDecorView) {
+ if (view instanceof DecorCaptionView) {
// The decor was most likely saved from a relaunch - so reuse it.
- nonClientDecorView = (NonClientDecorView) view;
+ DecorCaptionView = (DecorCaptionView) view;
removeViewAt(i);
}
}
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
- boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+ final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
attrs.type == TYPE_APPLICATION;
- // Only a non floating application window on one of the allowed workspaces can get a non
- // client decor.
- final boolean hasNonClientDecor = ActivityManager.StackId.hasWindowDecor(mWorkspaceId);
- if (!mWindow.isFloating() && isApplication && hasNonClientDecor) {
+ // Only a non floating application window on one of the allowed workspaces can get a caption
+ if (!mWindow.isFloating() && isApplication
+ && ActivityManager.StackId.hasWindowDecor(mStackId)) {
// Dependent on the brightness of the used title we either use the
// dark or the light button frame.
- if (nonClientDecorView == null) {
+ if (DecorCaptionView == 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);
+ DecorCaptionView = (DecorCaptionView) inflater.inflate(
+ R.layout.decor_caption_dark, null);
} else {
- nonClientDecorView = (NonClientDecorView) inflater.inflate(
- R.layout.non_client_decor_light, null);
+ DecorCaptionView = (DecorCaptionView) inflater.inflate(
+ R.layout.decor_caption_light, null);
}
}
- nonClientDecorView.setPhoneWindow(mWindow,
- ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
- ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
+ DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
} else {
- nonClientDecorView = null;
+ DecorCaptionView = null;
}
- // Tell the decor if it has a visible non client decor.
- enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor);
- return nonClientDecorView;
+ // Tell the decor if it has a visible caption.
+ enableCaption(DecorCaptionView != null);
+ return DecorCaptionView;
}
/**
@@ -1652,12 +1649,12 @@
}
/**
- * 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.
+ * Returns the Id of the stack which contains this window.
+ * Note that if no stack can be determined - which usually means that it was not
+ * created for an activity - the fullscreen stack ID will be returned.
+ * @return Returns the stack id which contains this window.
**/
- private int getWorkspaceId() {
+ private int getStackId() {
int workspaceId = INVALID_STACK_ID;
final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
if (callback != null) {
@@ -1674,12 +1671,12 @@
}
void clearContentView() {
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getChildCount() > 1) {
- mNonClientDecorView.removeViewAt(1);
+ if (mDecorCaptionView != null) {
+ if (mDecorCaptionView.getChildCount() > 1) {
+ mDecorCaptionView.removeViewAt(1);
}
} else {
- // This window doesn't have non client decor, so we need to just remove the
+ // This window doesn't have caption, so we need to just remove the
// children of the decor view.
removeAllViews();
}
@@ -1749,18 +1746,60 @@
}
}
+ /**
+ * 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;
+ updateElevation();
+ }
+
private void updateElevation() {
- if (mNonClientDecorView != null) {
- mNonClientDecorView.updateElevation();
+ float elevation = 0;
+ final boolean wasAdjustedForStack = mElevationAdjustedForStack;
+ // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
+ // since the shadow is bound to the content size and not the target size.
+ if (ActivityManager.StackId.hasWindowShadow(mStackId)
+ && mBackdropFrameRenderer == null) {
+ elevation = hasWindowFocus() ?
+ 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) {
+ elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
+ }
+ // Convert the DP elevation into physical pixels.
+ elevation = dipToPx(elevation);
+ mElevationAdjustedForStack = true;
+ } else {
+ mElevationAdjustedForStack = false;
+ }
+
+ // Don't change the elevation if we didn't previously adjust it for the stack it was in
+ // or it didn't change.
+ if ((wasAdjustedForStack || mElevationAdjustedForStack)
+ && getElevation() != elevation) {
+ mWindow.setElevation(elevation);
}
}
boolean isShowingCaption() {
- return mNonClientDecorView != null && mNonClientDecorView.isShowingDecor();
+ return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing();
}
int getCaptionHeight() {
- return isShowingCaption() ? mNonClientDecorView.getDecorCaptionHeight() : 0;
+ return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
+ }
+
+ /**
+ * 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());
}
private static class ColorViewState {
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
new file mode 100644
index 0000000..e22bd10
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -0,0 +1,205 @@
+/*
+ * 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.widget;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.Window;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * This class represents the special screen elements to control a window on freeform
+ * environment.
+ * As such this class handles the following things:
+ * <ul>
+ * <li>The caption, containing the system buttons like maximize, close and such as well as
+ * allowing the user to drag the window around.</li>
+ * After creating the view, the function
+ * {@link #setPhoneWindow} needs to be called to make
+ * the connection to it's owning PhoneWindow.
+ * Note: At this time the application can change various attributes of the DecorView which
+ * will break things (in settle/unexpected ways):
+ * <ul>
+ * <li>setOutlineProvider</li>
+ * <li>setSurfaceFormat</li>
+ * <li>..</li>
+ * </ul>
+ */
+public class DecorCaptionView extends LinearLayout
+ implements View.OnClickListener, View.OnTouchListener {
+ private final static String TAG = "DecorCaptionView";
+ private PhoneWindow mOwner = null;
+ private boolean mShow = false;
+
+ // True if the window is being dragged.
+ private boolean mDragging = false;
+
+ // True when the left mouse button got released while dragging.
+ private boolean mLeftMouseButtonReleased;
+
+ public DecorCaptionView(Context context) {
+ super(context);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setPhoneWindow(PhoneWindow owner, boolean show) {
+ mOwner = owner;
+ mShow = show;
+ updateCaptionVisibility();
+ // By changing the outline provider to BOUNDS, the window can remove its
+ // background without removing the shadow.
+ mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+
+ findViewById(R.id.maximize_window).setOnClickListener(this);
+ findViewById(R.id.close_window).setOnClickListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+ // the old input device events get cancelled first. So no need to remember the kind of
+ // input device we are listening to.
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ if (!mShow) {
+ // When there is no caption we should not react to anything.
+ return false;
+ }
+ // A drag action is started if we aren't dragging already and the starting event is
+ // either a left mouse button or any other input device.
+ if (!mDragging &&
+ (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
+ mDragging = true;
+ mLeftMouseButtonReleased = false;
+ startMovingTask(e.getRawX(), e.getRawY());
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mDragging && !mLeftMouseButtonReleased) {
+ if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+ // There is no separate mouse button up call and if the user mixes mouse
+ // button drag actions, we stop dragging once he releases the button.
+ mLeftMouseButtonReleased = true;
+ break;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mDragging) {
+ break;
+ }
+ // Abort the ongoing dragging.
+ mDragging = false;
+ return true;
+ }
+ return mDragging;
+ }
+
+ /**
+ * The phone window configuration has changed and the caption needs to be updated.
+ * @param show True if the caption should be shown.
+ */
+ public void onConfigurationChanged(boolean show) {
+ mShow = show;
+ updateCaptionVisibility();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.maximize_window) {
+ maximizeWindow();
+ } else if (view.getId() == R.id.close_window) {
+ mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ // Make sure that we never get more then one client area in our view.
+ if (index >= 2 || getChildCount() >= 2) {
+ throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
+ }
+ super.addView(child, index, params);
+ }
+
+ /**
+ * 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)));
+ }
+
+ /**
+ * Updates the visibility of the caption.
+ **/
+ private void updateCaptionVisibility() {
+ // Don't show the caption if the window has e.g. entered full screen.
+ boolean invisible = isFillingScreen() || !mShow;
+ View caption = getChildAt(0);
+ caption.setVisibility(invisible ? GONE : VISIBLE);
+ caption.setOnTouchListener(this);
+ }
+
+ /**
+ * Maximize the window by moving it to the maximized workspace stack.
+ **/
+ private void maximizeWindow() {
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Cannot change task workspace.");
+ }
+ }
+ }
+
+ public boolean isCaptionShowing() {
+ return mShow;
+ }
+
+ public int getCaptionHeight() {
+ final View caption = getChildAt(0);
+ return (caption != null) ? caption.getHeight() : 0;
+ }
+}
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
deleted file mode 100644
index 33b8e05..0000000
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * 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.widget;
-
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.Window;
-import android.util.Log;
-import android.util.TypedValue;
-
-import com.android.internal.R;
-import com.android.internal.policy.DecorView;
-import com.android.internal.policy.PhoneWindow;
-
-/**
- * This class represents the special screen elements to control a window on free form
- * environment. All thse screen elements are added in the "non client area" which is the area of
- * the window which is handled by the OS and not the application.
- * As such this class handles the following things:
- * <ul>
- * <li>The caption, containing the system buttons like maximize, close and such as well as
- * allowing the user to drag the window around.</li>
- * <li>The shadow - which is changing dependent on the window focus.</li>
- * <li>The border around the client area (if there is one).</li>
- * <li>The resize handles which allow to resize the window.</li>
- * </ul>
- * After creating the view, the function
- * {@link #setPhoneWindow} needs to be called to make
- * the connection to it's owning PhoneWindow.
- * Note: At this time the application can change various attributes of the DecorView which
- * will break things (in settle/unexpected ways):
- * <ul>
- * <li>setElevation</li>
- * <li>setOutlineProvider</li>
- * <li>setSurfaceFormat</li>
- * <li>..</li>
- * </ul>
- * This will be mitigated once b/22527834 will be addressed.
- */
-public class NonClientDecorView extends LinearLayout
- implements View.OnClickListener, View.OnTouchListener {
- 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;
- // The height of a window which has not in DIP.
- private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
- private PhoneWindow mOwner = null;
- private boolean mWindowHasShadow = false;
- private boolean mShowDecor = false;
-
- // True if the window is being dragged.
- private boolean mDragging = false;
-
- // True when the left mouse button got released while dragging.
- private boolean mLeftMouseButtonReleased;
-
- // True if this window is resizable (which is currently only true when the decor is shown).
- public boolean mVisible = false;
-
- // The current focus state of the window for updating the window elevation.
- private boolean mWindowHasFocus = true;
-
- // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
- // size calculation takes the shadow size into account. We set the elevation currently
- // to max until the first layout command has been executed.
- private boolean mAllowUpdateElevation = false;
-
- public NonClientDecorView(Context context) {
- super(context);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
- mOwner = owner;
- mWindowHasShadow = windowHasShadow;
- mShowDecor = showDecor;
- updateCaptionVisibility();
- if (mWindowHasShadow) {
- initializeElevation();
- }
- // By changing the outline provider to BOUNDS, the window can remove its
- // background without removing the shadow.
- mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
-
- findViewById(R.id.maximize_window).setOnClickListener(this);
- findViewById(R.id.close_window).setOnClickListener(this);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent e) {
- // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
- // the old input device events get cancelled first. So no need to remember the kind of
- // input device we are listening to.
- switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (!mShowDecor) {
- // When there is no decor we should not react to anything.
- return false;
- }
- // A drag action is started if we aren't dragging already and the starting event is
- // either a left mouse button or any other input device.
- if (!mDragging &&
- (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
- mDragging = true;
- mLeftMouseButtonReleased = false;
- startMovingTask(e.getRawX(), e.getRawY());
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mDragging && !mLeftMouseButtonReleased) {
- if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
- // There is no separate mouse button up call and if the user mixes mouse
- // button drag actions, we stop dragging once he releases the button.
- mLeftMouseButtonReleased = true;
- break;
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (!mDragging) {
- break;
- }
- // Abort the ongoing dragging.
- mDragging = false;
- return true;
- }
- return mDragging;
- }
-
- /**
- * 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 onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
- mShowDecor = showDecor;
- updateCaptionVisibility();
- if (windowHasShadow != mWindowHasShadow) {
- mWindowHasShadow = windowHasShadow;
- initializeElevation();
- }
- }
-
- @Override
- public void onClick(View view) {
- if (view.getId() == R.id.maximize_window) {
- maximizeWindow();
- } else if (view.getId() == R.id.close_window) {
- mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- mWindowHasFocus = hasWindowFocus;
- updateElevation();
- super.onWindowFocusChanged(hasWindowFocus);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // If the application changed its SystemUI metrics, we might also have to adapt
- // our shadow elevation.
- updateElevation();
- mAllowUpdateElevation = true;
-
- super.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.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);
- }
-
- /**
- * 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)));
- }
-
- /**
- * Updates the visibility of the caption.
- **/
- private void updateCaptionVisibility() {
- // Don't show the decor if the window has e.g. entered full screen.
- boolean invisible = isFillingScreen() || !mShowDecor;
- View caption = getChildAt(0);
- caption.setVisibility(invisible ? GONE : VISIBLE);
- caption.setOnTouchListener(this);
- mVisible = !invisible;
- }
-
- /**
- * 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".
- **/
- public void updateElevation() {
- float elevation = 0;
- // 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
- && ((DecorView) mOwner.getDecorView()).mBackdropFrameRenderer == null) {
- boolean fill = isFillingScreen();
- 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) {
- elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
- }
- // Convert the DP elevation into physical pixels.
- elevation = dipToPx(elevation);
- }
- // Don't change the elevation if it didn't change since it can require some time.
- if (mOwner.getDecorView().getElevation() != elevation) {
- mOwner.setElevation(elevation);
- }
- }
-
- /**
- * 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 maximized workspace stack.
- **/
- private void maximizeWindow() {
- Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
- if (callback != null) {
- try {
- callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
- } catch (RemoteException ex) {
- Log.e(TAG, "Cannot change task workspace.");
- }
- }
- }
-
- public boolean isShowingDecor() {
- return mShowDecor;
- }
-
- public int getDecorCaptionHeight() {
- final View caption = getChildAt(0);
- return (caption != null) ? caption.getHeight() : 0;
- }
-}
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/decor_caption_title.xml
similarity index 84%
rename from core/res/res/drawable/non_client_decor_title.xml
rename to core/res/res/drawable/decor_caption_title.xml
index e50daea..591605d3 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/core/res/res/drawable/decor_caption_title.xml
@@ -16,6 +16,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="true"
- android:drawable="@drawable/non_client_decor_title_focused" />
- <item android:drawable="@drawable/non_client_decor_title_unfocused" />
+ android:drawable="@drawable/decor_caption_title_focused" />
+ <item android:drawable="@drawable/decor_caption_title_unfocused" />
</selector>
diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_focused.xml
rename to core/res/res/drawable/decor_caption_title_focused.xml
diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_unfocused.xml
rename to core/res/res/drawable/decor_caption_title_unfocused.xml
diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/decor_caption_dark.xml
similarity index 90%
rename from core/res/res/layout/non_client_decor_dark.xml
rename to core/res/res/layout/decor_caption_dark.xml
index 40b8960..86d68af 100644
--- a/core/res/res/layout/non_client_decor_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -26,7 +26,7 @@
android:layout_width="match_parent"
android:layout_gravity="end"
android:layout_height="wrap_content"
- android:background="@drawable/non_client_decor_title"
+ android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<LinearLayout
@@ -53,4 +53,4 @@
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark" />
</LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/decor_caption_light.xml
similarity index 90%
rename from core/res/res/layout/non_client_decor_light.xml
rename to core/res/res/layout/decor_caption_light.xml
index c75d526..ee03545 100644
--- a/core/res/res/layout/non_client_decor_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -17,7 +17,7 @@
*/
-->
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -26,7 +26,7 @@
android:layout_width="match_parent"
android:layout_gravity="end"
android:layout_height="wrap_content"
- android:background="@drawable/non_client_decor_title"
+ android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<LinearLayout
@@ -53,4 +53,4 @@
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_light" />
</LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 556467a..7ce9d8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1957,9 +1957,9 @@
<java-symbol type="id" name="maximize_window" />
<java-symbol type="id" name="close_window" />
<java-symbol type="id" name="client_decor_placeholder" />
- <java-symbol type="layout" name="non_client_decor_light" />
- <java-symbol type="layout" name="non_client_decor_dark" />
- <java-symbol type="drawable" name="non_client_decor_title_focused" />
+ <java-symbol type="layout" name="decor_caption_light" />
+ <java-symbol type="layout" name="decor_caption_dark" />
+ <java-symbol type="drawable" name="decor_caption_title_focused" />
<!-- From TelephonyProvider -->
<java-symbol type="xml" name="apns" />