1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19import android.graphics.Rect;
20import android.util.Slog;
21
22import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
23import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
24import static com.android.server.wm.WindowManagerService.TAG;
25
26import java.io.PrintWriter;
27
28public class StackBox {
29    /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */
30    public static final int TASK_STACK_GOES_BEFORE = 0;
31    /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */
32    public static final int TASK_STACK_GOES_AFTER = 1;
33    /** Used with {@link WindowManagerService#createStack}. Horizontal to left of. */
34    public static final int TASK_STACK_TO_LEFT_OF = 2;
35    /** Used with {@link WindowManagerService#createStack}. Horizontal to right of. */
36    public static final int TASK_STACK_TO_RIGHT_OF = 3;
37    /** Used with {@link WindowManagerService#createStack}. Vertical: lower t/b Rect values. */
38    public static final int TASK_STACK_GOES_ABOVE = 4;
39    /** Used with {@link WindowManagerService#createStack}. Vertical: higher t/b Rect values. */
40    public static final int TASK_STACK_GOES_BELOW = 5;
41    /** Used with {@link WindowManagerService#createStack}. Put on a higher layer on display. */
42    public static final int TASK_STACK_GOES_OVER = 6;
43    /** Used with {@link WindowManagerService#createStack}. Put on a lower layer on display. */
44    public static final int TASK_STACK_GOES_UNDER = 7;
45
46    static int sCurrentBoxId = 0;
47
48    /** Unique id for this box */
49    final int mStackBoxId;
50
51    /** The service */
52    final WindowManagerService mService;
53
54    /** The display this box sits in. */
55    final DisplayContent mDisplayContent;
56
57    /** Non-null indicates this is mFirst or mSecond of a parent StackBox. Null indicates this
58     * is this entire size of mDisplayContent. */
59    StackBox mParent;
60
61    /** First child, this is null exactly when mStack is non-null. */
62    StackBox mFirst;
63
64    /** Second child, this is null exactly when mStack is non-null. */
65    StackBox mSecond;
66
67    /** Stack of Tasks, this is null exactly when mFirst and mSecond are non-null. */
68    TaskStack mStack;
69
70    /** Content limits relative to the DisplayContent this sits in. */
71    Rect mBounds = new Rect();
72
73    /** Relative orientation of mFirst and mSecond. */
74    boolean mVertical;
75
76    /** Fraction of mBounds to devote to mFirst, remainder goes to mSecond */
77    float mWeight;
78
79    /** Dirty flag. Something inside this or some descendant of this has changed. */
80    boolean layoutNeeded;
81
82    /** True if this StackBox sits below the Status Bar. */
83    boolean mUnderStatusBar;
84
85    /** Used to keep from reallocating a temporary Rect for propagating bounds to child boxes */
86    Rect mTmpRect = new Rect();
87
88    StackBox(WindowManagerService service, DisplayContent displayContent, StackBox parent) {
89        synchronized (StackBox.class) {
90            mStackBoxId = sCurrentBoxId++;
91        }
92
93        mService = service;
94        mDisplayContent = displayContent;
95        mParent = parent;
96    }
97
98    /** Propagate #layoutNeeded bottom up. */
99    void makeDirty() {
100        layoutNeeded = true;
101        if (mParent != null) {
102            mParent.makeDirty();
103        }
104    }
105
106    /**
107     * Determine if a particular StackBox is this one or a descendant of this one.
108     * @param stackBoxId The StackBox being searched for.
109     * @return true if the specified StackBox matches this or one of its descendants.
110     */
111    boolean contains(int stackBoxId) {
112        return mStackBoxId == stackBoxId ||
113                (mStack == null &&  (mFirst.contains(stackBoxId) || mSecond.contains(stackBoxId)));
114    }
115
116    /**
117     * Return the stackId of the stack that intersects the passed point.
118     * @param x coordinate of point.
119     * @param y coordinate of point.
120     * @return -1 if point is outside of mBounds, otherwise the stackId of the containing stack.
121     */
122    int stackIdFromPoint(int x, int y) {
123        if (!mBounds.contains(x, y)) {
124            return -1;
125        }
126        if (mStack != null) {
127            return mStack.mStackId;
128        }
129        int stackId = mFirst.stackIdFromPoint(x, y);
130        if (stackId >= 0) {
131            return stackId;
132        }
133        return mSecond.stackIdFromPoint(x, y);
134    }
135
136    /** Determine if this StackBox is the first child or second child.
137     * @return true if this is the first child.
138     */
139    boolean isFirstChild() {
140        return mParent != null && mParent.mFirst == this;
141    }
142
143    /** Returns the bounds of the specified TaskStack if it is contained in this StackBox.
144     * @param stackId the TaskStack to find the bounds of.
145     * @return a new Rect with the bounds of stackId if it is within this StackBox, null otherwise.
146     */
147    Rect getStackBounds(int stackId) {
148        if (mStack != null) {
149            return mStack.mStackId == stackId ? new Rect(mBounds) : null;
150        }
151        Rect bounds = mFirst.getStackBounds(stackId);
152        if (bounds != null) {
153            return bounds;
154        }
155        return mSecond.getStackBounds(stackId);
156    }
157
158    /**
159     * Create a new TaskStack relative to a specified one by splitting the StackBox containing
160     * the specified TaskStack into two children. The size and position each of the new StackBoxes
161     * is determined by the passed parameters.
162     * @param stackId The id of the new TaskStack to create.
163     * @param relativeStackBoxId The id of the StackBox to place the new TaskStack next to.
164     * @param position One of the static TASK_STACK_GOES_xxx positions defined in this class.
165     * @param weight The percentage size of the parent StackBox to devote to the new TaskStack.
166     * @return The new TaskStack.
167     */
168    TaskStack split(int stackId, int relativeStackBoxId, int position, float weight) {
169        if (mStackBoxId != relativeStackBoxId) {
170            // This is not the targeted StackBox.
171            if (mStack != null) {
172                return null;
173            }
174            // Propagate the split to see if the targeted StackBox is in either sub box.
175            TaskStack stack = mFirst.split(stackId, relativeStackBoxId, position, weight);
176            if (stack != null) {
177                return stack;
178            }
179            return mSecond.split(stackId, relativeStackBoxId, position, weight);
180        }
181
182        // Found it!
183        TaskStack stack = new TaskStack(mService, stackId, mDisplayContent);
184        TaskStack firstStack;
185        TaskStack secondStack;
186        if (position == TASK_STACK_GOES_BEFORE) {
187            // TODO: Test Configuration here for LTR/RTL.
188            position = TASK_STACK_TO_LEFT_OF;
189        } else if (position == TASK_STACK_GOES_AFTER) {
190            // TODO: Test Configuration here for LTR/RTL.
191            position = TASK_STACK_TO_RIGHT_OF;
192        }
193        switch (position) {
194            default:
195            case TASK_STACK_TO_LEFT_OF:
196            case TASK_STACK_TO_RIGHT_OF:
197                mVertical = false;
198                if (position == TASK_STACK_TO_LEFT_OF) {
199                    mWeight = weight;
200                    firstStack = stack;
201                    secondStack = mStack;
202                } else {
203                    mWeight = 1.0f - weight;
204                    firstStack = mStack;
205                    secondStack = stack;
206                }
207                break;
208            case TASK_STACK_GOES_ABOVE:
209            case TASK_STACK_GOES_BELOW:
210                mVertical = true;
211                if (position == TASK_STACK_GOES_ABOVE) {
212                    mWeight = weight;
213                    firstStack = stack;
214                    secondStack = mStack;
215                } else {
216                    mWeight = 1.0f - weight;
217                    firstStack = mStack;
218                    secondStack = stack;
219                }
220                break;
221        }
222
223        mFirst = new StackBox(mService, mDisplayContent, this);
224        firstStack.mStackBox = mFirst;
225        mFirst.mStack = firstStack;
226
227        mSecond = new StackBox(mService, mDisplayContent, this);
228        secondStack.mStackBox = mSecond;
229        mSecond.mStack = secondStack;
230
231        mStack = null;
232        return stack;
233    }
234
235    /** Return the stackId of the first mFirst StackBox with a non-null mStack */
236    int getStackId() {
237        if (mStack != null) {
238            return mStack.mStackId;
239        }
240        return mFirst.getStackId();
241    }
242
243    /** Remove this box and propagate its sibling's content up to their parent.
244     * @return The first stackId of the resulting StackBox. */
245    int remove() {
246        mDisplayContent.layoutNeeded = true;
247
248        if (mParent == null) {
249            // This is the top-plane stack.
250            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing top plane.");
251            mDisplayContent.removeStackBox(this);
252            return HOME_STACK_ID;
253        }
254
255        StackBox sibling = isFirstChild() ? mParent.mSecond : mParent.mFirst;
256        StackBox grandparent = mParent.mParent;
257        sibling.mParent = grandparent;
258        if (grandparent == null) {
259            // mParent is a top-plane stack. Now sibling will be.
260            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent null");
261            mDisplayContent.removeStackBox(mParent);
262            mDisplayContent.addStackBox(sibling, true);
263        } else {
264            if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent getting sibling");
265            if (mParent.isFirstChild()) {
266                grandparent.mFirst = sibling;
267            } else {
268                grandparent.mSecond = sibling;
269            }
270        }
271        return sibling.getStackId();
272    }
273
274    boolean resize(int stackBoxId, float weight) {
275        if (mStackBoxId != stackBoxId) {
276            return mStack == null &&
277                    (mFirst.resize(stackBoxId, weight) || mSecond.resize(stackBoxId, weight));
278        }
279        // Don't change weight on topmost stack.
280        if (mParent != null) {
281            mParent.mWeight = isFirstChild() ? weight : 1.0f - weight;
282        }
283        return true;
284    }
285
286    /** If this is a terminal StackBox (contains a TaskStack) set the bounds.
287     * @param bounds The rectangle to set the bounds to.
288     * @param underStatusBar True if the StackBox is directly below the Status Bar.
289     * @return True if the bounds changed, false otherwise. */
290    boolean setStackBoxSizes(Rect bounds, boolean underStatusBar) {
291        boolean change = false;
292        if (mUnderStatusBar != underStatusBar) {
293            change = true;
294            mUnderStatusBar = underStatusBar;
295        }
296        if (mStack != null) {
297            change |= !mBounds.equals(bounds);
298            if (change) {
299                mBounds.set(bounds);
300                mStack.setBounds(bounds, underStatusBar);
301            }
302        } else {
303            mTmpRect.set(bounds);
304            if (mVertical) {
305                final int height = bounds.height();
306                int firstHeight = (int)(height * mWeight);
307                mTmpRect.bottom = bounds.top + firstHeight;
308                change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar);
309                mTmpRect.top = mTmpRect.bottom;
310                mTmpRect.bottom = bounds.top + height;
311                change |= mSecond.setStackBoxSizes(mTmpRect, false);
312            } else {
313                final int width = bounds.width();
314                int firstWidth = (int)(width * mWeight);
315                mTmpRect.right = bounds.left + firstWidth;
316                change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar);
317                mTmpRect.left = mTmpRect.right;
318                mTmpRect.right = bounds.left + width;
319                change |= mSecond.setStackBoxSizes(mTmpRect, underStatusBar);
320            }
321        }
322        return change;
323    }
324
325    void resetAnimationBackgroundAnimator() {
326        if (mStack != null) {
327            mStack.resetAnimationBackgroundAnimator();
328            return;
329        }
330        mFirst.resetAnimationBackgroundAnimator();
331        mSecond.resetAnimationBackgroundAnimator();
332    }
333
334    boolean animateDimLayers() {
335        if (mStack != null) {
336            return mStack.animateDimLayers();
337        }
338        boolean result = mFirst.animateDimLayers();
339        result |= mSecond.animateDimLayers();
340        return result;
341    }
342
343    void resetDimming() {
344        if (mStack != null) {
345            mStack.resetDimmingTag();
346            return;
347        }
348        mFirst.resetDimming();
349        mSecond.resetDimming();
350    }
351
352    boolean isDimming() {
353        if (mStack != null) {
354            return mStack.isDimming();
355        }
356        boolean result = mFirst.isDimming();
357        result |= mSecond.isDimming();
358        return result;
359    }
360
361    void stopDimmingIfNeeded() {
362        if (mStack != null) {
363            mStack.stopDimmingIfNeeded();
364            return;
365        }
366        mFirst.stopDimmingIfNeeded();
367        mSecond.stopDimmingIfNeeded();
368    }
369
370    void switchUserStacks(int userId) {
371        if (mStack != null) {
372            mStack.switchUser(userId);
373            return;
374        }
375        mFirst.switchUserStacks(userId);
376        mSecond.switchUserStacks(userId);
377    }
378
379    void close() {
380        if (mStack != null) {
381            mStack.mDimLayer.mDimSurface.destroy();
382            mStack.mAnimationBackgroundSurface.mDimSurface.destroy();
383            return;
384        }
385        mFirst.close();
386        mSecond.close();
387    }
388
389    public void dump(String prefix, PrintWriter pw) {
390        pw.print(prefix); pw.print("mParent="); pw.println(mParent);
391        pw.print(prefix); pw.print("mBounds="); pw.print(mBounds.toShortString());
392            pw.print(" mVertical="); pw.print(mVertical);
393            pw.print(" layoutNeeded="); pw.println(layoutNeeded);
394        if (mFirst != null) {
395            pw.print(prefix); pw.print("mFirst="); pw.println(System.identityHashCode(mFirst));
396            mFirst.dump(prefix + "  ", pw);
397            pw.print(prefix); pw.print("mSecond="); pw.println(System.identityHashCode(mSecond));
398            mSecond.dump(prefix + "  ", pw);
399        } else {
400            pw.print(prefix); pw.print("mStack="); pw.println(mStack);
401            mStack.dump(prefix + "  ", pw);
402        }
403    }
404
405    @Override
406    public String toString() {
407        if (mStack != null) {
408            return "Box{" + hashCode() + " stack=" + mStack.mStackId + "}";
409        }
410        return "Box{" + hashCode() + " parent=" + System.identityHashCode(mParent)
411                + " first=" + System.identityHashCode(mFirst)
412                + " second=" + System.identityHashCode(mSecond) + "}";
413    }
414}
415