Showing docked stack divider window.
When there is docked stack, we want to show a "handle" through which the
user will resize the docked stack. We add it from the system process,
because we want to avoid IPC calls.
Change-Id: If15fd2a0fcb7077446d1ba6c533acb3028a42039
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 04d382d..3974205 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2003,6 +2003,7 @@
case TYPE_SYSTEM_DIALOG:
case TYPE_VOLUME_OVERLAY:
case TYPE_PRIVATE_PRESENTATION:
+ case TYPE_DOCK_DIVIDER:
break;
}
@@ -2134,54 +2135,56 @@
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
return 13;
+ case TYPE_DOCK_DIVIDER:
+ return 14;
case TYPE_KEYGUARD_SCRIM:
// the safety window that shows behind keyguard while keyguard is starting
- return 14;
- case TYPE_STATUS_BAR_SUB_PANEL:
return 15;
- case TYPE_STATUS_BAR:
+ case TYPE_STATUS_BAR_SUB_PANEL:
return 16;
- case TYPE_STATUS_BAR_PANEL:
+ case TYPE_STATUS_BAR:
return 17;
- case TYPE_KEYGUARD_DIALOG:
+ case TYPE_STATUS_BAR_PANEL:
return 18;
+ case TYPE_KEYGUARD_DIALOG:
+ return 19;
case TYPE_VOLUME_OVERLAY:
// the on-screen volume indicator and controller shown when the user
// changes the device volume
- return 19;
+ return 20;
case TYPE_SYSTEM_OVERLAY:
// the on-screen volume indicator and controller shown when the user
// changes the device volume
- return 20;
+ return 21;
case TYPE_NAVIGATION_BAR:
// the navigation bar, if available, shows atop most things
- return 21;
+ return 22;
case TYPE_NAVIGATION_BAR_PANEL:
// some panels (e.g. search) need to show on top of the navigation bar
- return 22;
+ return 23;
case TYPE_SYSTEM_ERROR:
// system-level error dialogs
- return 23;
+ return 24;
case TYPE_MAGNIFICATION_OVERLAY:
// used to highlight the magnified portion of a display
- return 24;
+ return 25;
case TYPE_DISPLAY_OVERLAY:
// used to simulate secondary display devices
- return 25;
+ return 26;
case TYPE_DRAG:
// the drag layer: input for drag-and-drop is associated with this window,
// which sits above all other focusable windows
- return 26;
+ return 27;
case TYPE_ACCESSIBILITY_OVERLAY:
// overlay put by accessibility services to intercept user interaction
- return 27;
- case TYPE_SECURE_SYSTEM_OVERLAY:
return 28;
- case TYPE_BOOT_PROGRESS:
+ case TYPE_SECURE_SYSTEM_OVERLAY:
return 29;
+ case TYPE_BOOT_PROGRESS:
+ return 30;
case TYPE_POINTER:
// the (mouse) pointer layer
- return 30;
+ return 31;
}
Log.e(TAG, "Unknown window type: " + type);
return 2;
@@ -2271,6 +2274,7 @@
case TYPE_WALLPAPER:
case TYPE_DREAM:
case TYPE_KEYGUARD_SCRIM:
+ case TYPE_DOCK_DIVIDER:
return false;
default:
// Hide only windows below the keyguard host window.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb0dba8..4392ab4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.app.ActivityManager.HOME_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
@@ -111,6 +112,8 @@
/** Remove this display when animation on it has completed. */
boolean mDeferredRemoval;
+ final DockedStackDividerController mDividerControllerLocked;
+
/**
* @param display May not be null.
* @param service You know.
@@ -122,6 +125,7 @@
display.getMetrics(mDisplayMetrics);
isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
mService = service;
+ mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
}
int getDisplayId() {
@@ -552,4 +556,14 @@
public String toString() {
return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mStacks;
}
+
+ TaskStack getDockedStack() {
+ for (int i = mStacks.size() - 1; i >= 0; i--) {
+ TaskStack stack = mStacks.get(i);
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ return stack;
+ }
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
new file mode 100644
index 0000000..ad207d4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 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.server.wm;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+/**
+ * Controls showing and hiding of a docked stack divider on the display.
+ */
+public class DockedStackDividerController {
+ private static final String TAG = "DockedStackDivider";
+ private final Context mContext;
+ private final int mDividerWidth;
+ private final DisplayContent mDisplayContent;
+ private View mView;
+ private Rect mTmpRect = new Rect();
+
+ DockedStackDividerController(Context context, DisplayContent displayContent) {
+ mContext = context;
+ mDisplayContent = displayContent;
+ mDividerWidth = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ }
+
+ private void addDivider() {
+ View view = LayoutInflater.from(mContext).inflate(
+ com.android.internal.R.layout.docked_stack_divider, null);
+ WindowManagerGlobal manager = WindowManagerGlobal.getInstance();
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ mDividerWidth, MATCH_PARENT, TYPE_DOCK_DIVIDER,
+ FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
+ | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+ PixelFormat.OPAQUE);
+ params.setTitle(TAG);
+ manager.addView(view, params, mDisplayContent.getDisplay(), null);
+ mView = view;
+ }
+
+ private void removeDivider() {
+ WindowManagerGlobal manager = WindowManagerGlobal.getInstance();
+ manager.removeView(mView, true /* immediate */);
+ mView = null;
+ }
+
+ boolean hasDivider() {
+ return mView != null;
+ }
+
+ void update() {
+ TaskStack stack = mDisplayContent.getDockedStack();
+ if (stack != null && mView == null) {
+ addDivider();
+ } else if (stack == null && mView != null) {
+ removeDivider();
+ }
+ }
+
+ int getWidth() {
+ return mDividerWidth;
+ }
+
+
+ void positionDockedStackedDivider(Rect frame) {
+ TaskStack stack = mDisplayContent.getDockedStack();
+ if (stack == null) {
+ // Unfortunately we might end up with still having a divider, even though the underlying
+ // stack was already removed. This is because we are on AM thread and the removal of the
+ // divider was deferred to WM thread and hasn't happened yet.
+ return;
+ }
+ final @TaskStack.DockSide int side = stack.getDockSide();
+ stack.getBounds(mTmpRect);
+ switch (side) {
+ case TaskStack.DOCKED_LEFT:
+ frame.set(mTmpRect.right, frame.top, mTmpRect.right + frame.width(), frame.bottom);
+ break;
+ case TaskStack.DOCKED_TOP:
+ frame.set(frame.left, mTmpRect.bottom, mTmpRect.right,
+ mTmpRect.bottom + frame.height());
+ break;
+ case TaskStack.DOCKED_RIGHT:
+ frame.set(mTmpRect.left - frame.width(), frame.top, mTmpRect.left, frame.bottom);
+ break;
+ case TaskStack.DOCKED_BOTTOM:
+ frame.set(frame.left, mTmpRect.top - frame.height(), frame.right, mTmpRect.top);
+ break;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4541dd6..6ebff42 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
@@ -432,6 +433,10 @@
return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
}
+ boolean inDockedWorkspace() {
+ return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
+ }
+
@Override
public boolean isFullscreen() {
return mFullscreen;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 1362555..96fcf93 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -352,7 +352,8 @@
// the docked stack occupies a dedicated region on screen.
bounds = new Rect();
displayContent.getLogicalDisplayRect(mTmpRect);
- getInitialDockedStackBounds(mTmpRect, bounds, mStackId);
+ getInitialDockedStackBounds(mTmpRect, bounds, mStackId,
+ mDisplayContent.mDividerControllerLocked.getWidth() / 2);
}
updateDisplayInfo(bounds);
@@ -371,27 +372,29 @@
* @param displayRect The bounds of the display the docked stack is on.
* @param outBounds Output bounds that should be used for the stack.
* @param stackId Id of stack we are calculating the bounds for.
+ * @param adjustment
*/
- private static void getInitialDockedStackBounds(
- Rect displayRect, Rect outBounds, int stackId) {
+ private static void getInitialDockedStackBounds(Rect displayRect, Rect outBounds, int stackId,
+ int adjustment) {
// Docked stack start off occupying half the screen space.
+ final boolean dockedStack = stackId == DOCKED_STACK_ID;
final boolean splitHorizontally = displayRect.width() > displayRect.height();
final boolean topOrLeftCreateMode =
WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
- final boolean placeTopOrLeft = (stackId == DOCKED_STACK_ID && topOrLeftCreateMode)
- || (stackId != DOCKED_STACK_ID && !topOrLeftCreateMode);
+ final boolean placeTopOrLeft = (dockedStack && topOrLeftCreateMode)
+ || (!dockedStack && !topOrLeftCreateMode);
outBounds.set(displayRect);
if (placeTopOrLeft) {
if (splitHorizontally) {
- outBounds.right = displayRect.centerX();
+ outBounds.right = displayRect.centerX() - adjustment;
} else {
- outBounds.bottom = displayRect.centerY();
+ outBounds.bottom = displayRect.centerY() - adjustment;
}
} else {
if (splitHorizontally) {
- outBounds.left = displayRect.centerX();
+ outBounds.left = displayRect.centerX() + adjustment;
} else {
- outBounds.top = displayRect.centerY();
+ outBounds.top = displayRect.centerY() + adjustment;
}
}
}
@@ -404,7 +407,8 @@
private void resizeNonDockedStacks(boolean fullscreen) {
mDisplayContent.getLogicalDisplayRect(mTmpRect);
if (!fullscreen) {
- getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID);
+ getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID,
+ mDisplayContent.mDividerControllerLocked.getWidth());
}
final int count = mService.mStackIdToStack.size();
@@ -546,14 +550,14 @@
final int orientation = mService.mCurConfiguration.orientation;
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// Portrait mode, docked either at the top or the bottom.
- if (mTmpRect.top - mBounds.top < mTmpRect.bottom - mBounds.bottom) {
+ if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) {
return DOCKED_TOP;
} else {
return DOCKED_BOTTOM;
}
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape mode, docked either on the left or on the right.
- if (mTmpRect.left - mBounds.left < mTmpRect.right - mBounds.right) {
+ if (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) {
return DOCKED_LEFT;
} else {
return DOCKED_RIGHT;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 29598ab..d510c4a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -33,6 +34,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
@@ -921,6 +923,7 @@
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
+
LocalServices.addService(WindowManagerInternal.class, new LocalService());
initPolicy();
@@ -1854,6 +1857,11 @@
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
+ } else if (type == TYPE_DOCK_DIVIDER) {
+ if (displayContent.mDividerControllerLocked.hasDivider()) {
+ Slog.w(TAG, "Attempted to add docked stack divider twice. Aborting.");
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
} else if (token.appWindowToken != null) {
Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type);
// It is not valid to use an app token with other system types; we will
@@ -4521,7 +4529,10 @@
}
stack.attachDisplayContent(displayContent);
displayContent.attachStack(stack, onTop);
-
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ mH.obtainMessage(H.UPDATE_DOCKED_STACK_DIVIDER,
+ displayContent).sendToTarget();
+ }
moveStackWindowsLocked(displayContent);
final WindowList windows = displayContent.getWindowList();
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
@@ -4544,6 +4555,11 @@
void detachStackLocked(DisplayContent displayContent, TaskStack stack) {
displayContent.detachStack(stack);
stack.detachDisplay();
+ // We can't directly remove the divider, because only the WM thread can do these operations
+ // and we can be on AM thread.
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ mH.obtainMessage(H.UPDATE_DOCKED_STACK_DIVIDER, displayContent).sendToTarget();
+ }
}
public void detachStack(int stackId) {
@@ -7220,6 +7236,8 @@
public static final int TAP_DOWN_OUTSIDE_TASK = 40;
public static final int FINISH_TASK_POSITIONING = 41;
+ public static final int UPDATE_DOCKED_STACK_DIVIDER = 42;
+
@Override
public void handleMessage(Message msg) {
if (DEBUG_WINDOW_TRACE) {
@@ -7759,6 +7777,13 @@
}
}
break;
+ case UPDATE_DOCKED_STACK_DIVIDER: {
+ DisplayContent content = (DisplayContent) msg.obj;
+ synchronized (mWindowMap) {
+ content.mDividerControllerLocked.update();
+ }
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG, "handleMessage: exit");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a6478a0..64440d3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -707,6 +708,8 @@
mContentFrame.set(mFrame);
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
+ } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+ mDisplayContent.mDividerControllerLocked.positionDockedStackedDivider(mFrame);
} else {
mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
Math.max(mContentFrame.top, mFrame.top),