| /* |
| * Copyright (C) 2013 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 android.graphics.Rect; |
| import android.util.Slog; |
| |
| import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; |
| import static com.android.server.wm.WindowManagerService.DEBUG_STACK; |
| import static com.android.server.wm.WindowManagerService.TAG; |
| |
| import java.io.PrintWriter; |
| |
| public class StackBox { |
| /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */ |
| public static final int TASK_STACK_GOES_BEFORE = 0; |
| /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */ |
| public static final int TASK_STACK_GOES_AFTER = 1; |
| /** Used with {@link WindowManagerService#createStack}. Horizontal to left of. */ |
| public static final int TASK_STACK_TO_LEFT_OF = 2; |
| /** Used with {@link WindowManagerService#createStack}. Horizontal to right of. */ |
| public static final int TASK_STACK_TO_RIGHT_OF = 3; |
| /** Used with {@link WindowManagerService#createStack}. Vertical: lower t/b Rect values. */ |
| public static final int TASK_STACK_GOES_ABOVE = 4; |
| /** Used with {@link WindowManagerService#createStack}. Vertical: higher t/b Rect values. */ |
| public static final int TASK_STACK_GOES_BELOW = 5; |
| /** Used with {@link WindowManagerService#createStack}. Put on a higher layer on display. */ |
| public static final int TASK_STACK_GOES_OVER = 6; |
| /** Used with {@link WindowManagerService#createStack}. Put on a lower layer on display. */ |
| public static final int TASK_STACK_GOES_UNDER = 7; |
| |
| static int sCurrentBoxId = 0; |
| |
| /** Unique id for this box */ |
| final int mStackBoxId; |
| |
| /** The service */ |
| final WindowManagerService mService; |
| |
| /** The display this box sits in. */ |
| final DisplayContent mDisplayContent; |
| |
| /** Non-null indicates this is mFirst or mSecond of a parent StackBox. Null indicates this |
| * is this entire size of mDisplayContent. */ |
| StackBox mParent; |
| |
| /** First child, this is null exactly when mStack is non-null. */ |
| StackBox mFirst; |
| |
| /** Second child, this is null exactly when mStack is non-null. */ |
| StackBox mSecond; |
| |
| /** Stack of Tasks, this is null exactly when mFirst and mSecond are non-null. */ |
| TaskStack mStack; |
| |
| /** Content limits relative to the DisplayContent this sits in. */ |
| Rect mBounds = new Rect(); |
| |
| /** Relative orientation of mFirst and mSecond. */ |
| boolean mVertical; |
| |
| /** Fraction of mBounds to devote to mFirst, remainder goes to mSecond */ |
| float mWeight; |
| |
| /** Dirty flag. Something inside this or some descendant of this has changed. */ |
| boolean layoutNeeded; |
| |
| /** True if this StackBox sits below the Status Bar. */ |
| boolean mUnderStatusBar; |
| |
| /** Used to keep from reallocating a temporary Rect for propagating bounds to child boxes */ |
| Rect mTmpRect = new Rect(); |
| |
| StackBox(WindowManagerService service, DisplayContent displayContent, StackBox parent) { |
| synchronized (StackBox.class) { |
| mStackBoxId = sCurrentBoxId++; |
| } |
| |
| mService = service; |
| mDisplayContent = displayContent; |
| mParent = parent; |
| } |
| |
| /** Propagate #layoutNeeded bottom up. */ |
| void makeDirty() { |
| layoutNeeded = true; |
| if (mParent != null) { |
| mParent.makeDirty(); |
| } |
| } |
| |
| /** |
| * Determine if a particular StackBox is this one or a descendant of this one. |
| * @param stackBoxId The StackBox being searched for. |
| * @return true if the specified StackBox matches this or one of its descendants. |
| */ |
| boolean contains(int stackBoxId) { |
| return mStackBoxId == stackBoxId || mFirst.contains(stackBoxId) |
| || mSecond.contains(stackBoxId); |
| } |
| |
| /** |
| * Return the stackId of the stack that intersects the passed point. |
| * @param x coordinate of point. |
| * @param y coordinate of point. |
| * @return -1 if point is outside of mBounds, otherwise the stackId of the containing stack. |
| */ |
| int stackIdFromPoint(int x, int y) { |
| if (!mBounds.contains(x, y)) { |
| return -1; |
| } |
| if (mStack != null) { |
| return mStack.mStackId; |
| } |
| int stackId = mFirst.stackIdFromPoint(x, y); |
| if (stackId >= 0) { |
| return stackId; |
| } |
| return mSecond.stackIdFromPoint(x, y); |
| } |
| |
| /** Determine if this StackBox is the first child or second child. |
| * @return true if this is the first child. |
| */ |
| boolean isFirstChild() { |
| return mParent != null && mParent.mFirst == this; |
| } |
| |
| /** Returns the bounds of the specified TaskStack if it is contained in this StackBox. |
| * @param stackId the TaskStack to find the bounds of. |
| * @return a new Rect with the bounds of stackId if it is within this StackBox, null otherwise. |
| */ |
| Rect getStackBounds(int stackId) { |
| if (mStack != null) { |
| return mStack.mStackId == stackId ? new Rect(mBounds) : null; |
| } |
| Rect bounds = mFirst.getStackBounds(stackId); |
| if (bounds != null) { |
| return bounds; |
| } |
| return mSecond.getStackBounds(stackId); |
| } |
| |
| /** |
| * Create a new TaskStack relative to a specified one by splitting the StackBox containing |
| * the specified TaskStack into two children. The size and position each of the new StackBoxes |
| * is determined by the passed parameters. |
| * @param stackId The id of the new TaskStack to create. |
| * @param relativeStackBoxId The id of the StackBox to place the new TaskStack next to. |
| * @param position One of the static TASK_STACK_GOES_xxx positions defined in this class. |
| * @param weight The percentage size of the parent StackBox to devote to the new TaskStack. |
| * @return The new TaskStack. |
| */ |
| TaskStack split(int stackId, int relativeStackBoxId, int position, float weight) { |
| if (mStackBoxId != relativeStackBoxId) { |
| // This is not the targeted StackBox. |
| if (mStack != null) { |
| return null; |
| } |
| // Propagate the split to see if the targeted StackBox is in either sub box. |
| TaskStack stack = mFirst.split(stackId, relativeStackBoxId, position, weight); |
| if (stack != null) { |
| return stack; |
| } |
| return mSecond.split(stackId, relativeStackBoxId, position, weight); |
| } |
| |
| // Found it! |
| TaskStack stack = new TaskStack(mService, stackId, mDisplayContent); |
| TaskStack firstStack; |
| TaskStack secondStack; |
| if (position == TASK_STACK_GOES_BEFORE) { |
| // TODO: Test Configuration here for LTR/RTL. |
| position = TASK_STACK_TO_LEFT_OF; |
| } else if (position == TASK_STACK_GOES_AFTER) { |
| // TODO: Test Configuration here for LTR/RTL. |
| position = TASK_STACK_TO_RIGHT_OF; |
| } |
| switch (position) { |
| default: |
| case TASK_STACK_TO_LEFT_OF: |
| case TASK_STACK_TO_RIGHT_OF: |
| mVertical = false; |
| if (position == TASK_STACK_TO_LEFT_OF) { |
| mWeight = weight; |
| firstStack = stack; |
| secondStack = mStack; |
| } else { |
| mWeight = 1.0f - weight; |
| firstStack = mStack; |
| secondStack = stack; |
| } |
| break; |
| case TASK_STACK_GOES_ABOVE: |
| case TASK_STACK_GOES_BELOW: |
| mVertical = true; |
| if (position == TASK_STACK_GOES_ABOVE) { |
| mWeight = weight; |
| firstStack = stack; |
| secondStack = mStack; |
| } else { |
| mWeight = 1.0f - weight; |
| firstStack = mStack; |
| secondStack = stack; |
| } |
| break; |
| } |
| |
| mFirst = new StackBox(mService, mDisplayContent, this); |
| firstStack.mStackBox = mFirst; |
| mFirst.mStack = firstStack; |
| |
| mSecond = new StackBox(mService, mDisplayContent, this); |
| secondStack.mStackBox = mSecond; |
| mSecond.mStack = secondStack; |
| |
| mStack = null; |
| return stack; |
| } |
| |
| /** Return the stackId of the first mFirst StackBox with a non-null mStack */ |
| int getStackId() { |
| if (mStack != null) { |
| return mStack.mStackId; |
| } |
| return mFirst.getStackId(); |
| } |
| |
| /** Remove this box and propagate its sibling's content up to their parent. |
| * @return The first stackId of the resulting StackBox. */ |
| int remove() { |
| if (mStack != null) { |
| if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId); |
| mDisplayContent.mStackHistory.remove(mStack); |
| } |
| mDisplayContent.layoutNeeded = true; |
| |
| if (mParent == null) { |
| // This is the top-plane stack. |
| if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing top plane."); |
| mDisplayContent.removeStackBox(this); |
| return HOME_STACK_ID; |
| } |
| |
| StackBox sibling = isFirstChild() ? mParent.mSecond : mParent.mFirst; |
| StackBox grandparent = mParent.mParent; |
| sibling.mParent = grandparent; |
| if (grandparent == null) { |
| // mParent is a top-plane stack. Now sibling will be. |
| if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent null"); |
| mDisplayContent.removeStackBox(mParent); |
| mDisplayContent.addStackBox(sibling, true); |
| } else { |
| if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent getting sibling"); |
| if (mParent.isFirstChild()) { |
| grandparent.mFirst = sibling; |
| } else { |
| grandparent.mSecond = sibling; |
| } |
| } |
| return sibling.getStackId(); |
| } |
| |
| boolean resize(int stackBoxId, float weight) { |
| if (mStackBoxId != stackBoxId) { |
| return mStack == null && |
| (mFirst.resize(stackBoxId, weight) || mSecond.resize(stackBoxId, weight)); |
| } |
| // Don't change weight on topmost stack. |
| if (mParent != null) { |
| mParent.mWeight = isFirstChild() ? weight : 1.0f - weight; |
| } |
| return true; |
| } |
| |
| /** If this is a terminal StackBox (contains a TaskStack) set the bounds. |
| * @param bounds The rectangle to set the bounds to. |
| * @param underStatusBar True if the StackBox is directly below the Status Bar. |
| * @return True if the bounds changed, false otherwise. */ |
| boolean setStackBoxSizes(Rect bounds, boolean underStatusBar) { |
| boolean change = false; |
| if (mUnderStatusBar != underStatusBar) { |
| change = true; |
| mUnderStatusBar = underStatusBar; |
| } |
| if (mStack != null) { |
| change |= !mBounds.equals(bounds); |
| if (change) { |
| mBounds.set(bounds); |
| mStack.setBounds(bounds, underStatusBar); |
| } |
| } else { |
| mTmpRect.set(bounds); |
| if (mVertical) { |
| final int height = bounds.height(); |
| int firstHeight = (int)(height * mWeight); |
| mTmpRect.bottom = bounds.top + firstHeight; |
| change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar); |
| mTmpRect.top = mTmpRect.bottom; |
| mTmpRect.bottom = bounds.top + height; |
| change |= mSecond.setStackBoxSizes(mTmpRect, false); |
| } else { |
| final int width = bounds.width(); |
| int firstWidth = (int)(width * mWeight); |
| mTmpRect.right = bounds.left + firstWidth; |
| change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar); |
| mTmpRect.left = mTmpRect.right; |
| mTmpRect.right = bounds.left + width; |
| change |= mSecond.setStackBoxSizes(mTmpRect, underStatusBar); |
| } |
| } |
| return change; |
| } |
| |
| void resetAnimationBackgroundAnimator() { |
| if (mStack != null) { |
| mStack.resetAnimationBackgroundAnimator(); |
| return; |
| } |
| mFirst.resetAnimationBackgroundAnimator(); |
| mSecond.resetAnimationBackgroundAnimator(); |
| } |
| |
| boolean animateDimLayers() { |
| if (mStack != null) { |
| return mStack.animateDimLayers(); |
| } |
| boolean result = mFirst.animateDimLayers(); |
| result |= mSecond.animateDimLayers(); |
| return result; |
| } |
| |
| void resetDimming() { |
| if (mStack != null) { |
| mStack.resetDimmingTag(); |
| return; |
| } |
| mFirst.resetDimming(); |
| mSecond.resetDimming(); |
| } |
| |
| boolean isDimming() { |
| if (mStack != null) { |
| return mStack.isDimming(); |
| } |
| boolean result = mFirst.isDimming(); |
| result |= mSecond.isDimming(); |
| return result; |
| } |
| |
| void stopDimmingIfNeeded() { |
| if (mStack != null) { |
| mStack.stopDimmingIfNeeded(); |
| return; |
| } |
| mFirst.stopDimmingIfNeeded(); |
| mSecond.stopDimmingIfNeeded(); |
| } |
| |
| public void dump(String prefix, PrintWriter pw) { |
| pw.print(prefix); pw.print("mParent="); pw.println(mParent); |
| pw.print(prefix); pw.print("mBounds="); pw.print(mBounds.toShortString()); |
| pw.print(" mVertical="); pw.print(mVertical); |
| pw.print(" layoutNeeded="); pw.println(layoutNeeded); |
| if (mFirst != null) { |
| pw.print(prefix); pw.print("mFirst="); pw.println(System.identityHashCode(mFirst)); |
| mFirst.dump(prefix + " ", pw); |
| pw.print(prefix); pw.print("mSecond="); pw.println(System.identityHashCode(mSecond)); |
| mSecond.dump(prefix + " ", pw); |
| } else { |
| pw.print(prefix); pw.print("mStack="); pw.println(mStack); |
| mStack.dump(prefix + " ", pw); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| if (mStack != null) { |
| return "Box{" + hashCode() + " stack=" + mStack.mStackId + "}"; |
| } |
| return "Box{" + hashCode() + " parent=" + System.identityHashCode(mParent) |
| + " first=" + System.identityHashCode(mFirst) |
| + " second=" + System.identityHashCode(mSecond) + "}"; |
| } |
| } |