TaskStack.java revision 8e89b31a62fb9ec5ad33908c5e8e9c7ab2fd949f
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 static android.app.ActivityManager.*;
20import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
21import static com.android.server.wm.WindowManagerService.TAG;
22
23import android.content.res.Configuration;
24import android.graphics.Rect;
25import android.os.Debug;
26import android.os.RemoteException;
27import android.util.EventLog;
28import android.util.Slog;
29import android.util.SparseArray;
30import android.view.DisplayInfo;
31
32import com.android.server.EventLogTags;
33
34import java.io.PrintWriter;
35import java.util.ArrayList;
36
37public class TaskStack implements DimLayer.DimLayerUser {
38
39    // If the stack should be resized to fullscreen.
40    private static final boolean FULLSCREEN = true;
41
42    /** Unique identifier */
43    final int mStackId;
44
45    /** The service */
46    private final WindowManagerService mService;
47
48    /** The display this stack sits under. */
49    private DisplayContent mDisplayContent;
50
51    /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
52     * mTaskHistory in the ActivityStack with the same mStackId */
53    private final ArrayList<Task> mTasks = new ArrayList<>();
54
55    /** For comparison with DisplayContent bounds. */
56    private Rect mTmpRect = new Rect();
57
58    /** Content limits relative to the DisplayContent this sits in. */
59    private Rect mBounds = new Rect();
60
61    /** Whether mBounds is fullscreen */
62    private boolean mFullscreen = true;
63
64    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
65    DimLayer mAnimationBackgroundSurface;
66
67    /** The particular window with an Animation with non-zero background color. */
68    WindowStateAnimator mAnimationBackgroundAnimator;
69
70    /** Application tokens that are exiting, but still on screen for animations. */
71    final AppTokenList mExitingAppTokens = new AppTokenList();
72
73    /** Detach this stack from its display when animation completes. */
74    boolean mDeferDetach;
75
76    TaskStack(WindowManagerService service, int stackId) {
77        mService = service;
78        mStackId = stackId;
79        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
80    }
81
82    DisplayContent getDisplayContent() {
83        return mDisplayContent;
84    }
85
86    ArrayList<Task> getTasks() {
87        return mTasks;
88    }
89
90    void resizeWindows() {
91        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
92            mTasks.get(taskNdx).resizeWindows();
93        }
94    }
95
96    boolean allowTaskResize() {
97        return mStackId == FREEFORM_WORKSPACE_STACK_ID
98                || mStackId == DOCKED_STACK_ID;
99    }
100
101    /**
102     * Set the bounds of the stack and its containing tasks.
103     * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
104     * @param resizeTasks If true, the tasks within the stack will also be resized.
105     * @param configs Configuration for individual tasks, keyed by task id.
106     * @param taskBounds Bounds for individual tasks, keyed by task id.
107     * @return True if the stack bounds was changed.
108     * */
109    boolean setBounds(Rect stackBounds, boolean resizeTasks, SparseArray<Configuration> configs,
110            SparseArray<Rect> taskBounds) {
111        if (!setBounds(stackBounds)) {
112            return false;
113        }
114
115        if (!resizeTasks) {
116            return true;
117        }
118
119        // Update bounds of containing tasks.
120        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
121            final Task task = mTasks.get(taskNdx);
122            Configuration config = configs.get(task.mTaskId);
123            if (config != null) {
124                Rect bounds = taskBounds.get(task.mTaskId);
125                if (bounds == null) {
126                    bounds = stackBounds;
127                }
128                task.setBounds(bounds, config);
129            } else {
130                Slog.wtf(TAG, "No config for task: " + task + ", is there a mismatch with AM?");
131            }
132        }
133        return true;
134    }
135
136    private boolean setBounds(Rect bounds) {
137        boolean oldFullscreen = mFullscreen;
138        if (mDisplayContent != null) {
139            mDisplayContent.getLogicalDisplayRect(mTmpRect);
140            if (bounds == null) {
141                bounds = mTmpRect;
142                mFullscreen = true;
143            } else {
144                // ensure bounds are entirely within the display rect
145                if (!bounds.intersect(mTmpRect)) {
146                    // Can't set bounds outside the containing display.. Sorry!
147                    return false;
148                }
149                mFullscreen = mTmpRect.equals(bounds);
150            }
151        }
152
153        if (bounds == null) {
154            // Can't set to fullscreen if we don't have a display to get bounds from...
155            return false;
156        }
157        if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) {
158            return false;
159        }
160
161        mAnimationBackgroundSurface.setBounds(bounds);
162        mBounds.set(bounds);
163        return true;
164    }
165
166    void getBounds(Rect out) {
167        out.set(mBounds);
168    }
169
170    void updateDisplayInfo(Rect bounds) {
171        if (mDisplayContent != null) {
172            if (bounds != null) {
173                setBounds(bounds);
174            } else {
175                setBounds(mFullscreen ? null : mBounds);
176            }
177            for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
178                mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent);
179            }
180        }
181    }
182
183    boolean isAnimating() {
184        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
185            final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
186            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
187                final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows;
188                for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
189                    final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator;
190                    if (winAnimator.isAnimating() || winAnimator.mWin.mExiting) {
191                        return true;
192                    }
193                }
194            }
195        }
196        return false;
197    }
198
199    void addTask(Task task, boolean toTop) {
200        addTask(task, toTop, task.showForAllUsers());
201    }
202
203    /**
204     * Put a Task in this stack. Used for adding and moving.
205     * @param task The task to add.
206     * @param toTop Whether to add it to the top or bottom.
207     * @param showForAllUsers Whether to show the task regardless of the current user.
208     */
209    void addTask(Task task, boolean toTop, boolean showForAllUsers) {
210        positionTask(task, toTop ? mTasks.size() : 0, showForAllUsers);
211    }
212
213    void positionTask(Task task, int position, boolean showForAllUsers) {
214        final boolean canShowTask =
215                showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
216        mTasks.remove(task);
217        int stackSize = mTasks.size();
218        int minPosition = 0;
219        int maxPosition = stackSize;
220
221        if (canShowTask) {
222            minPosition = computeMinPosition(minPosition, stackSize);
223        } else {
224            maxPosition = computeMaxPosition(maxPosition);
225        }
226        // Reset position based on minimum/maximum possible positions.
227        position = Math.min(Math.max(position, minPosition), maxPosition);
228
229        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG,
230                "positionTask: task=" + task + " position=" + position);
231        mTasks.add(position, task);
232
233        task.mStack = this;
234        task.updateDisplayInfo(mDisplayContent);
235        boolean toTop = position == mTasks.size() - 1;
236        if (toTop) {
237            mDisplayContent.moveStack(this, true);
238        }
239        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, position);
240    }
241
242    /** Calculate the minimum possible position for a task that can be shown to the user.
243     *  The minimum position will be above all other tasks that can't be shown.
244     *  @param minPosition The minimum position the caller is suggesting.
245     *                  We will start adjusting up from here.
246     *  @param size The size of the current task list.
247     */
248    private int computeMinPosition(int minPosition, int size) {
249        while (minPosition < size) {
250            final Task tmpTask = mTasks.get(minPosition);
251            final boolean canShowTmpTask =
252                    tmpTask.showForAllUsers()
253                            || mService.isCurrentProfileLocked(tmpTask.mUserId);
254            if (canShowTmpTask) {
255                break;
256            }
257            minPosition++;
258        }
259        return minPosition;
260    }
261
262    /** Calculate the maximum possible position for a task that can't be shown to the user.
263     *  The maximum position will be below all other tasks that can be shown.
264     *  @param maxPosition The maximum position the caller is suggesting.
265     *                  We will start adjusting down from here.
266     */
267    private int computeMaxPosition(int maxPosition) {
268        while (maxPosition > 0) {
269            final Task tmpTask = mTasks.get(maxPosition - 1);
270            final boolean canShowTmpTask =
271                    tmpTask.showForAllUsers()
272                            || mService.isCurrentProfileLocked(tmpTask.mUserId);
273            if (!canShowTmpTask) {
274                break;
275            }
276            maxPosition--;
277        }
278        return maxPosition;
279    }
280
281    void moveTaskToTop(Task task) {
282        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers="
283                + Debug.getCallers(6));
284        mTasks.remove(task);
285        addTask(task, true);
286    }
287
288    void moveTaskToBottom(Task task) {
289        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task);
290        mTasks.remove(task);
291        addTask(task, false);
292    }
293
294    /**
295     * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the
296     * back.
297     * @param task The Task to delete.
298     */
299    void removeTask(Task task) {
300        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task);
301        mTasks.remove(task);
302        if (mDisplayContent != null) {
303            if (mTasks.isEmpty()) {
304                mDisplayContent.moveStack(this, false);
305            }
306            mDisplayContent.layoutNeeded = true;
307        }
308        for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
309            final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
310            if (wtoken.mTask == task) {
311                wtoken.mIsExiting = false;
312                mExitingAppTokens.remove(appNdx);
313            }
314        }
315    }
316
317    void attachDisplayContent(DisplayContent displayContent) {
318        if (mDisplayContent != null) {
319            throw new IllegalStateException("attachDisplayContent: Already attached");
320        }
321
322        mDisplayContent = displayContent;
323        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId());
324
325        Rect bounds = null;
326        final boolean dockedStackExists = mService.mStackIdToStack.get(DOCKED_STACK_ID) != null;
327        if (mStackId == DOCKED_STACK_ID || (dockedStackExists
328                && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
329            // The existence of a docked stack affects the size of any static stack created since
330            // the docked stack occupies a dedicated region on screen.
331            bounds = new Rect();
332            displayContent.getLogicalDisplayRect(mTmpRect);
333            getInitialDockedStackBounds(mTmpRect, bounds, mStackId);
334        }
335
336        updateDisplayInfo(bounds);
337
338        if (mStackId == DOCKED_STACK_ID) {
339            // Attaching a docked stack to the display affects the size of all other static
340            // stacks since the docked stack occupies a dedicated region on screen.
341            // Resize existing static stacks so they are pushed to the side of the docked stack.
342            resizeNonDockedStacks(!FULLSCREEN);
343        }
344    }
345
346    /**
347     * Outputs the initial bounds a stack should be given the presence of a docked stack on the
348     * display.
349     * @param displayRect The bounds of the display the docked stack is on.
350     * @param outBounds Output bounds that should be used for the stack.
351     * @param stackId Id of stack we are calculating the bounds for.
352     */
353    private static void getInitialDockedStackBounds(
354            Rect displayRect, Rect outBounds, int stackId) {
355        // Docked stack start off occupying half the screen space.
356        // TODO(multi-window): Need to support the selecting which half of the screen the
357        // docked stack uses for snapping windows to the edge of the screen.
358        final boolean splitHorizontally = displayRect.width() > displayRect.height();
359        outBounds.set(displayRect);
360        if (stackId == DOCKED_STACK_ID) {
361            if (splitHorizontally) {
362                outBounds.right = displayRect.centerX();
363            } else {
364                outBounds.bottom = displayRect.centerY();
365            }
366        } else {
367            if (splitHorizontally) {
368                outBounds.left = displayRect.centerX();
369            } else {
370                outBounds.top = displayRect.centerY();
371            }
372        }
373    }
374
375    /** Resizes all non-docked stacks in the system to either fullscreen or the appropriate size
376     * based on the presence of a docked stack.
377     * @param fullscreen If true the stacks will be resized to fullscreen, else they will be
378     *                   resized to the appropriate size based on the presence of a docked stack.
379     */
380    private void resizeNonDockedStacks(boolean fullscreen) {
381        mDisplayContent.getLogicalDisplayRect(mTmpRect);
382        if (!fullscreen) {
383            getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID);
384        }
385
386        final int count = mService.mStackIdToStack.size();
387        for (int i = 0; i < count; i++) {
388            final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
389            final int otherStackId = otherStack.mStackId;
390            if (otherStackId != DOCKED_STACK_ID
391                    && otherStackId >= FIRST_STATIC_STACK_ID
392                    && otherStackId <= LAST_STATIC_STACK_ID) {
393                try {
394                    mService.mActivityManager.resizeStack(otherStackId, mTmpRect);
395                } catch (RemoteException e) {
396                    // This will not happen since we are in the same process.
397                }
398            }
399        }
400    }
401
402    void detachDisplay() {
403        EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
404
405        boolean doAnotherLayoutPass = false;
406        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
407            final AppTokenList appWindowTokens = mTasks.get(taskNdx).mAppTokens;
408            for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) {
409                final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows;
410                for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) {
411                    // We are in the middle of changing the state of displays/stacks/tasks. We need
412                    // to finish that, before we let layout interfere with it.
413                    mService.removeWindowInnerLocked(appWindows.get(winNdx),
414                            false /* performLayout */);
415                    doAnotherLayoutPass = true;
416                }
417            }
418        }
419        if (doAnotherLayoutPass) {
420            mService.mWindowPlacerLocked.requestTraversal();
421        }
422
423        if (mStackId == DOCKED_STACK_ID) {
424            // Docked stack was detached from the display, so we no longer need to restrict the
425            // region of the screen other static stacks occupy. Go ahead and make them fullscreen.
426            resizeNonDockedStacks(FULLSCREEN);
427        }
428
429        close();
430    }
431
432    void resetAnimationBackgroundAnimator() {
433        mAnimationBackgroundAnimator = null;
434        mAnimationBackgroundSurface.hide();
435    }
436
437    void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
438        int animLayer = winAnimator.mAnimLayer;
439        if (mAnimationBackgroundAnimator == null
440                || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
441            mAnimationBackgroundAnimator = winAnimator;
442            animLayer = mService.adjustAnimationBackground(winAnimator);
443            mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM,
444                    ((color >> 24) & 0xff) / 255f, 0);
445        }
446    }
447
448    void switchUser() {
449        int top = mTasks.size();
450        for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
451            Task task = mTasks.get(taskNdx);
452            if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
453                mTasks.remove(taskNdx);
454                mTasks.add(task);
455                --top;
456            }
457        }
458    }
459
460    void close() {
461        if (mAnimationBackgroundSurface != null) {
462            mAnimationBackgroundSurface.destroySurface();
463            mAnimationBackgroundSurface = null;
464        }
465        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
466            mTasks.get(taskNdx).close();
467        }
468        mDisplayContent = null;
469    }
470
471    public void dump(String prefix, PrintWriter pw) {
472        pw.print(prefix); pw.print("mStackId="); pw.println(mStackId);
473        pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach);
474        for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) {
475            pw.print(prefix);
476            mTasks.get(taskNdx).printTo(prefix + " ", pw);
477        }
478        if (mAnimationBackgroundSurface.isDimming()) {
479            pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:");
480            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
481        }
482        if (!mExitingAppTokens.isEmpty()) {
483            pw.println();
484            pw.println("  Exiting application tokens:");
485            for (int i = mExitingAppTokens.size() - 1; i >= 0; i--) {
486                WindowToken token = mExitingAppTokens.get(i);
487                pw.print("  Exiting App #"); pw.print(i);
488                pw.print(' '); pw.print(token);
489                pw.println(':');
490                token.dump(pw, "    ");
491            }
492        }
493    }
494
495    @Override
496    public boolean isFullscreen() {
497        return mFullscreen;
498    }
499
500    @Override
501    public DisplayInfo getDisplayInfo() {
502        return mDisplayContent.getDisplayInfo();
503    }
504
505    @Override
506    public String toString() {
507        return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
508    }
509}
510