Task.java revision bd0d937303ae54d8a5bb5f08080c4164302daefc
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.StackId.DOCKED_STACK_ID;
20import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
21import static android.app.ActivityManager.StackId.HOME_STACK_ID;
22import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
23import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
24import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
26import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
27import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
28import static com.android.server.wm.WindowManagerService.H.SHOW_NON_RESIZEABLE_DOCK_TOAST;
29import static android.view.WindowManager.DOCKED_INVALID;
30import static android.view.WindowManager.DOCKED_LEFT;
31import static android.view.WindowManager.DOCKED_RIGHT;
32import static android.view.WindowManager.DOCKED_TOP;
33
34import android.app.ActivityManager.StackId;
35import android.content.res.Configuration;
36import android.graphics.Rect;
37import android.util.EventLog;
38import android.util.Slog;
39import android.view.DisplayInfo;
40import android.view.Surface;
41
42import com.android.server.EventLogTags;
43
44import java.io.PrintWriter;
45import java.util.ArrayList;
46
47class Task implements DimLayer.DimLayerUser {
48    // Return value from {@link setBounds} indicating no change was made to the Task bounds.
49    static final int BOUNDS_CHANGE_NONE = 0;
50    // Return value from {@link setBounds} indicating the position of the Task bounds changed.
51    static final int BOUNDS_CHANGE_POSITION = 1;
52    // Return value from {@link setBounds} indicating the size of the Task bounds changed.
53    static final int BOUNDS_CHANGE_SIZE = 1 << 1;
54
55    TaskStack mStack;
56    final AppTokenList mAppTokens = new AppTokenList();
57    final int mTaskId;
58    final int mUserId;
59    boolean mDeferRemoval = false;
60    final WindowManagerService mService;
61
62    // Content limits relative to the DisplayContent this sits in.
63    private Rect mBounds = new Rect();
64
65    // Device rotation as of the last time {@link #mBounds} was set.
66    int mRotation;
67
68    // Whether mBounds is fullscreen
69    private boolean mFullscreen = true;
70
71    // Contains configurations settings that are different from the global configuration due to
72    // stack specific operations. E.g. {@link #setBounds}.
73    Configuration mOverrideConfig;
74
75    // For comparison with DisplayContent bounds.
76    private Rect mTmpRect = new Rect();
77    // For handling display rotations.
78    private Rect mTmpRect2 = new Rect();
79
80    // Whether the task is resizeable
81    private boolean mResizeable;
82
83    // Whether we need to show toast about the app being non-resizeable when it becomes visible.
84    // This flag is set when a non-resizeable task is docked (or side-by-side). It's cleared
85    // after we show the toast.
86    private boolean mShowNonResizeableDockToast;
87
88    // Whether the task is currently being drag-resized
89    private boolean mDragResizing;
90
91    Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
92            Configuration config) {
93        mTaskId = taskId;
94        mStack = stack;
95        mUserId = userId;
96        mService = service;
97        setBounds(bounds, config);
98    }
99
100    DisplayContent getDisplayContent() {
101        return mStack.getDisplayContent();
102    }
103
104    void setShowNonResizeableDockToast() {
105        mShowNonResizeableDockToast = true;
106    }
107
108    void scheduleShowNonResizeableDockToastIfNeeded() {
109        if (!mShowNonResizeableDockToast) {
110            return;
111        }
112        final DisplayContent displayContent = mStack.getDisplayContent();
113        // If docked stack is not yet visible, we don't want to show the toast yet,
114        // since we need the visible rect of the docked task to position the toast.
115        if (displayContent == null || displayContent.getDockedStackLocked() == null) {
116            return;
117        }
118
119        mShowNonResizeableDockToast = false;
120
121        final int dockSide = mStack.getDockSide();
122        int xOffset = 0;
123        int yOffset = 0;
124        if (dockSide != DOCKED_INVALID) {
125            mStack.getBounds(mTmpRect);
126            displayContent.getLogicalDisplayRect(mTmpRect2);
127
128            if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
129                xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
130            } else if (dockSide == DOCKED_TOP) {
131                // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
132                yOffset = mTmpRect2.bottom - mTmpRect.bottom;
133            }
134            mService.mH.obtainMessage(
135                    SHOW_NON_RESIZEABLE_DOCK_TOAST, xOffset, yOffset).sendToTarget();
136        }
137    }
138
139    void addAppToken(int addPos, AppWindowToken wtoken) {
140        final int lastPos = mAppTokens.size();
141        if (addPos >= lastPos) {
142            addPos = lastPos;
143        } else {
144            for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
145                if (mAppTokens.get(pos).removed) {
146                    // addPos assumes removed tokens are actually gone.
147                    ++addPos;
148                }
149            }
150        }
151        mAppTokens.add(addPos, wtoken);
152        wtoken.mTask = this;
153        mDeferRemoval = false;
154    }
155
156    void removeLocked() {
157        if (!mAppTokens.isEmpty() && mStack.isAnimating()) {
158            if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: deferring removing taskId=" + mTaskId);
159            mDeferRemoval = true;
160            return;
161        }
162        if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: removing taskId=" + mTaskId);
163        EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
164        mDeferRemoval = false;
165        DisplayContent content = getDisplayContent();
166        if (content != null) {
167            content.mDimLayerController.removeDimLayerUser(this);
168        }
169        mStack.removeTask(this);
170        mService.mTaskIdToTask.delete(mTaskId);
171    }
172
173    void moveTaskToStack(TaskStack stack, boolean toTop) {
174        if (stack == mStack) {
175            return;
176        }
177        if (DEBUG_STACK) Slog.i(TAG_WM, "moveTaskToStack: removing taskId=" + mTaskId
178                + " from stack=" + mStack);
179        EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
180        if (mStack != null) {
181            mStack.removeTask(this);
182        }
183        stack.addTask(this, toTop);
184    }
185
186    void positionTaskInStack(TaskStack stack, int position, Rect bounds, Configuration config) {
187        if (mStack != null && stack != mStack) {
188            if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskInStack: removing taskId=" + mTaskId
189                    + " from stack=" + mStack);
190            EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
191            mStack.removeTask(this);
192        }
193        stack.positionTask(this, position, showForAllUsers());
194        setBounds(bounds, config);
195    }
196
197    boolean removeAppToken(AppWindowToken wtoken) {
198        boolean removed = mAppTokens.remove(wtoken);
199        if (mAppTokens.size() == 0) {
200            EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId,
201                    "removeAppToken: last token");
202            if (mDeferRemoval) {
203                removeLocked();
204            }
205        }
206        wtoken.mTask = null;
207        /* Leave mTaskId for now, it might be useful for debug
208        wtoken.mTaskId = -1;
209         */
210        return removed;
211    }
212
213    void setSendingToBottom(boolean toBottom) {
214        for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) {
215            mAppTokens.get(appTokenNdx).sendingToBottom = toBottom;
216        }
217    }
218
219    /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
220    int setBounds(Rect bounds, Configuration config) {
221        if (config == null) {
222            config = Configuration.EMPTY;
223        }
224        if (bounds == null && !Configuration.EMPTY.equals(config)) {
225            throw new IllegalArgumentException("null bounds but non empty configuration: "
226                    + config);
227        }
228        if (bounds != null && Configuration.EMPTY.equals(config)) {
229            throw new IllegalArgumentException("non null bounds, but empty configuration");
230        }
231        boolean oldFullscreen = mFullscreen;
232        int rotation = Surface.ROTATION_0;
233        final DisplayContent displayContent = mStack.getDisplayContent();
234        if (displayContent != null) {
235            displayContent.getLogicalDisplayRect(mTmpRect);
236            rotation = displayContent.getDisplayInfo().rotation;
237            if (bounds == null) {
238                bounds = mTmpRect;
239                mFullscreen = true;
240            } else {
241                mFullscreen = mTmpRect.equals(bounds);
242            }
243        }
244
245        if (bounds == null) {
246            // Can't set to fullscreen if we don't have a display to get bounds from...
247            return BOUNDS_CHANGE_NONE;
248        }
249        if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) {
250            return BOUNDS_CHANGE_NONE;
251        }
252
253        int boundsChange = BOUNDS_CHANGE_NONE;
254        if (mBounds.left != bounds.left || mBounds.top != bounds.top) {
255            boundsChange |= BOUNDS_CHANGE_POSITION;
256        }
257        if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) {
258            boundsChange |= BOUNDS_CHANGE_SIZE;
259        }
260
261        mBounds.set(bounds);
262        mRotation = rotation;
263        if (displayContent != null) {
264            displayContent.mDimLayerController.updateDimLayer(this);
265        }
266        mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
267        return boundsChange;
268    }
269
270    void setResizeable(boolean resizeable) {
271        mResizeable = resizeable;
272    }
273
274    boolean isResizeable() {
275        return mResizeable;
276    }
277
278    boolean resizeLocked(Rect bounds, Configuration configuration, boolean forced) {
279        int boundsChanged = setBounds(bounds, configuration);
280        if (forced) {
281            boundsChanged |= BOUNDS_CHANGE_SIZE;
282        }
283        if (boundsChanged == BOUNDS_CHANGE_NONE) {
284            return false;
285        }
286        if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
287            resizeWindows();
288        } else {
289            moveWindows();
290        }
291        return true;
292    }
293
294    boolean scrollLocked(Rect bounds) {
295        // shift the task bound if it doesn't fully cover the stack area
296        mStack.getDimBounds(mTmpRect);
297        if (mService.mCurConfiguration.orientation == ORIENTATION_LANDSCAPE) {
298            if (bounds.left > mTmpRect.left) {
299                bounds.left = mTmpRect.left;
300                bounds.right = mTmpRect.left + mBounds.width();
301            } else if (bounds.right < mTmpRect.right) {
302                bounds.left = mTmpRect.right - mBounds.width();
303                bounds.right = mTmpRect.right;
304            }
305        } else {
306            if (bounds.top > mTmpRect.top) {
307                bounds.top = mTmpRect.top;
308                bounds.bottom = mTmpRect.top + mBounds.height();
309            } else if (bounds.bottom < mTmpRect.bottom) {
310                bounds.top = mTmpRect.bottom - mBounds.height();
311                bounds.bottom = mTmpRect.bottom;
312            }
313        }
314
315        if (bounds.equals(mBounds)) {
316            return false;
317        }
318        // Normal setBounds() does not allow non-null bounds for fullscreen apps.
319        // We only change bounds for the scrolling case without change it size,
320        // on resizing path we should still want the validation.
321        mBounds.set(bounds);
322        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
323            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
324            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
325                final WindowState win = windows.get(winNdx);
326                win.mXOffset = bounds.left;
327                win.mYOffset = bounds.top;
328            }
329        }
330        return true;
331    }
332
333    /** Return true if the current bound can get outputted to the rest of the system as-is. */
334    private boolean useCurrentBounds() {
335        final DisplayContent displayContent = mStack.getDisplayContent();
336        if (mFullscreen
337                || !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
338                || displayContent == null
339                || displayContent.getDockedStackLocked() != null) {
340            return true;
341        }
342        return false;
343    }
344
345    /** Original bounds of the task if applicable, otherwise fullscreen rect. */
346    public void getBounds(Rect out) {
347        if (useCurrentBounds()) {
348            // No need to adjust the output bounds if fullscreen or the docked stack is visible
349            // since it is already what we want to represent to the rest of the system.
350            out.set(mBounds);
351            return;
352        }
353
354        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
355        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
356        // system.
357        mStack.getDisplayContent().getLogicalDisplayRect(out);
358    }
359
360
361    /**
362     * Calculate the maximum visible area of this task. If the task has only one app,
363     * the result will be visible frame of that app. If the task has more than one apps,
364     * we search from top down if the next app got different visible area.
365     *
366     * This effort is to handle the case where some task (eg. GMail composer) might pop up
367     * a dialog that's different in size from the activity below, in which case we should
368     * be dimming the entire task area behind the dialog.
369     *
370     * @param out Rect containing the max visible bounds.
371     * @return true if the task has some visible app windows; false otherwise.
372     */
373    boolean getMaxVisibleBounds(Rect out) {
374        boolean foundTop = false;
375        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
376            final AppWindowToken token = mAppTokens.get(i);
377            // skip hidden (or about to hide) apps
378            if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
379                continue;
380            }
381            final WindowState win = token.findMainWindow();
382            if (win == null) {
383                continue;
384            }
385            if (!foundTop) {
386                out.set(win.mVisibleFrame);
387                foundTop = true;
388                continue;
389            }
390            if (win.mVisibleFrame.left < out.left) {
391                out.left = win.mVisibleFrame.left;
392            }
393            if (win.mVisibleFrame.top < out.top) {
394                out.top = win.mVisibleFrame.top;
395            }
396            if (win.mVisibleFrame.right > out.right) {
397                out.right = win.mVisibleFrame.right;
398            }
399            if (win.mVisibleFrame.bottom > out.bottom) {
400                out.bottom = win.mVisibleFrame.bottom;
401            }
402        }
403        return foundTop;
404    }
405
406    /** Bounds of the task to be used for dimming, as well as touch related tests. */
407    @Override
408    public void getDimBounds(Rect out) {
409        if (useCurrentBounds()) {
410            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
411                return;
412            }
413
414            out.set(mBounds);
415            return;
416        }
417
418        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
419        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
420        // system.
421        mStack.getDisplayContent().getLogicalDisplayRect(out);
422    }
423
424    void setDragResizing(boolean dragResizing) {
425        mDragResizing = dragResizing;
426    }
427
428    boolean isDragResizing() {
429        return mDragResizing;
430    }
431
432    void updateDisplayInfo(final DisplayContent displayContent) {
433        if (displayContent == null) {
434            return;
435        }
436        if (mFullscreen) {
437            setBounds(null, Configuration.EMPTY);
438            return;
439        }
440        final int newRotation = displayContent.getDisplayInfo().rotation;
441        if (mRotation == newRotation) {
442            return;
443        }
444
445        // Device rotation changed. We don't want the task to move around on the screen when
446        // this happens, so update the task bounds so it stays in the same place.
447        mTmpRect2.set(mBounds);
448        displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
449        if (setBounds(mTmpRect2, mOverrideConfig) != BOUNDS_CHANGE_NONE) {
450            // Post message to inform activity manager of the bounds change simulating
451            // a one-way call. We do this to prevent a deadlock between window manager
452            // lock and activity manager lock been held. Only tasks within the freeform stack
453            // are resizeable independently of their stack resizing.
454            if (mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
455                mService.mH.sendMessage(mService.mH.obtainMessage(
456                        RESIZE_TASK, mTaskId, RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mBounds));
457            }
458        }
459    }
460
461    void resizeWindows() {
462        final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
463        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
464            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
465            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
466                final WindowState win = windows.get(winNdx);
467                if (!resizingWindows.contains(win)) {
468                    if (DEBUG_RESIZE) Slog.d(TAG_WM, "resizeWindows: Resizing " + win);
469                    resizingWindows.add(win);
470                }
471            }
472        }
473    }
474
475    void moveWindows() {
476        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
477            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
478            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
479                final WindowState win = windows.get(winNdx);
480                if (DEBUG_RESIZE) Slog.d(TAG_WM, "moveWindows: Moving " + win);
481                win.mMovedByResize = true;
482            }
483        }
484    }
485
486    /**
487     * Cancels any running app transitions associated with the task.
488     */
489    void cancelTaskWindowTransition() {
490        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
491            mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
492        }
493    }
494
495    /**
496     * Cancels any running thumbnail transitions associated with the task.
497     */
498    void cancelTaskThumbnailTransition() {
499        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
500            mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
501        }
502    }
503
504    boolean showForAllUsers() {
505        final int tokensCount = mAppTokens.size();
506        return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
507    }
508
509    boolean inHomeStack() {
510        return mStack != null && mStack.mStackId == HOME_STACK_ID;
511    }
512
513    boolean inFreeformWorkspace() {
514        return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
515    }
516
517    boolean inDockedWorkspace() {
518        return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
519    }
520
521    boolean isResizeableByDockedStack() {
522        return mStack != null && getDisplayContent().getDockedStackLocked() != null &&
523                StackId.isTaskResizeableByDockedStack(mStack.mStackId);
524    }
525
526    /**
527     * Whether the task should be treated as if it's docked. Returns true if the task
528     * is currently in docked workspace, or it's side-by-side to a docked task.
529     */
530    boolean isDockedInEffect() {
531        return inDockedWorkspace() || isResizeableByDockedStack();
532    }
533
534    WindowState getTopVisibleAppMainWindow() {
535        final AppWindowToken token = getTopVisibleAppToken();
536        return token != null ? token.findMainWindow() : null;
537    }
538
539    AppWindowToken getTopVisibleAppToken() {
540        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
541            final AppWindowToken token = mAppTokens.get(i);
542            // skip hidden (or about to hide) apps
543            if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
544                return token;
545            }
546        }
547        return null;
548    }
549
550    @Override
551    public boolean isFullscreen() {
552        if (useCurrentBounds()) {
553            return mFullscreen;
554        }
555        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
556        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
557        // system.
558        return true;
559    }
560
561    @Override
562    public DisplayInfo getDisplayInfo() {
563        return mStack.getDisplayContent().getDisplayInfo();
564    }
565
566    @Override
567    public String toString() {
568        return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
569    }
570
571    @Override
572    public String toShortString() {
573        return "Task=" + mTaskId;
574    }
575
576    public void printTo(String prefix, PrintWriter pw) {
577        pw.print(prefix); pw.print("taskId="); pw.println(mTaskId);
578            pw.print(prefix + prefix); pw.print("mFullscreen="); pw.println(mFullscreen);
579            pw.print(prefix + prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
580            pw.print(prefix + prefix); pw.print("mdr="); pw.println(mDeferRemoval);
581            pw.print(prefix + prefix); pw.print("appTokens="); pw.println(mAppTokens);
582    }
583}
584