/* * 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 static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerService.TAG; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Debug; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Slog; import android.util.TypedValue; import android.view.Surface; import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.ArrayList; public class TaskStack { /** Amount of time in milliseconds to animate the dim surface from one value to another, * when no window animation is driving it. */ private static final int DEFAULT_DIM_DURATION = 200; /** Unique identifier */ final int mStackId; /** The service */ private final WindowManagerService mService; /** The display this stack sits under. */ private DisplayContent mDisplayContent; /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match * mTaskHistory in the ActivityStack with the same mStackId */ private final ArrayList<Task> mTasks = new ArrayList<Task>(); /** For comparison with DisplayContent bounds. */ private Rect mTmpRect = new Rect(); /** For handling display rotations. */ private Rect mTmpRect2 = new Rect(); /** Content limits relative to the DisplayContent this sits in. */ private Rect mBounds = new Rect(); /** Whether mBounds is fullscreen */ private boolean mFullscreen = true; /** Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} */ private DimLayer mDimLayer; /** The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer. */ WindowStateAnimator mDimWinAnimator; /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */ DimLayer mAnimationBackgroundSurface; /** The particular window with an Animation with non-zero background color. */ WindowStateAnimator mAnimationBackgroundAnimator; /** Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the end * then stop any dimming. */ boolean mDimmingTag; /** Application tokens that are exiting, but still on screen for animations. */ final AppTokenList mExitingAppTokens = new AppTokenList(); /** Detach this stack from its display when animation completes. */ boolean mDeferDetach; // Contains configurations settings that are different from the global configuration due to // stack specific operations. E.g. {@link #setBounds}. Configuration mOverrideConfig; // True if the stack was forced to fullscreen disregarding the override configuration. private boolean mForceFullscreen; // The {@link #mBounds} before the stack was forced to fullscreen. Will be restored as the // stack bounds once the stack is no longer forced to fullscreen. final private Rect mPreForceFullscreenBounds; // Device rotation as of the last time {@link #mBounds} was set. int mRotation; TaskStack(WindowManagerService service, int stackId) { mService = service; mStackId = stackId; mOverrideConfig = Configuration.EMPTY; mForceFullscreen = false; mPreForceFullscreenBounds = new Rect(); // TODO: remove bounds from log, they are always 0. EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); } DisplayContent getDisplayContent() { return mDisplayContent; } ArrayList<Task> getTasks() { return mTasks; } void resizeWindows() { final ArrayList<WindowState> resizingWindows = mService.mResizingWindows; for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowState win = windows.get(winNdx); if (!resizingWindows.contains(win)) { if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG, "setBounds: Resizing " + win); resizingWindows.add(win); } } } } } /** Set the stack bounds. Passing in null sets the bounds to fullscreen. */ boolean setBounds(Rect bounds) { boolean oldFullscreen = mFullscreen; int rotation = Surface.ROTATION_0; if (mDisplayContent != null) { mDisplayContent.getLogicalDisplayRect(mTmpRect); rotation = mDisplayContent.getDisplayInfo().rotation; if (bounds == null) { bounds = mTmpRect; mFullscreen = true; } else { // ensure bounds are entirely within the display rect if (!bounds.intersect(mTmpRect)) { // Can't set bounds outside the containing display.. Sorry! return false; } mFullscreen = mTmpRect.equals(bounds); } } if (bounds == null) { // Can't set to fullscreen if we don't have a display to get bounds from... return false; } if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) { return false; } mDimLayer.setBounds(bounds); mAnimationBackgroundSurface.setBounds(bounds); mBounds.set(bounds); mRotation = rotation; updateOverrideConfiguration(); return true; } void getBounds(Rect out) { out.set(mBounds); } private void updateOverrideConfiguration() { final Configuration serviceConfig = mService.mCurConfiguration; if (mFullscreen) { mOverrideConfig = Configuration.EMPTY; return; } if (mOverrideConfig == Configuration.EMPTY) { mOverrideConfig = new Configuration(); } // TODO(multidisplay): Update Dp to that of display stack is on. final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; mOverrideConfig.screenWidthDp = Math.min((int)(mBounds.width() / density), serviceConfig.screenWidthDp); mOverrideConfig.screenHeightDp = Math.min((int)(mBounds.height() / density), serviceConfig.screenHeightDp); mOverrideConfig.smallestScreenWidthDp = Math.min(mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp); mOverrideConfig.orientation = (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; } void updateDisplayInfo() { if (mFullscreen) { setBounds(null); } else if (mDisplayContent != null) { final int newRotation = mDisplayContent.getDisplayInfo().rotation; if (mRotation == newRotation) { return; } // Device rotation changed. We don't want the stack to move around on the screen when // this happens, so update the stack bounds so it stays in the same place. final int rotationDelta = DisplayContent.deltaRotation(mRotation, newRotation); mDisplayContent.getLogicalDisplayRect(mTmpRect); switch (rotationDelta) { case Surface.ROTATION_0: mTmpRect2.set(mBounds); break; case Surface.ROTATION_90: mTmpRect2.top = mTmpRect.bottom - mBounds.right; mTmpRect2.left = mBounds.top; mTmpRect2.right = mTmpRect2.left + mBounds.height(); mTmpRect2.bottom = mTmpRect2.top + mBounds.width(); break; case Surface.ROTATION_180: mTmpRect2.top = mTmpRect.bottom - mBounds.bottom; mTmpRect2.left = mTmpRect.right - mBounds.right; mTmpRect2.right = mTmpRect2.left + mBounds.width(); mTmpRect2.bottom = mTmpRect2.top + mBounds.height(); break; case Surface.ROTATION_270: mTmpRect2.top = mBounds.left; mTmpRect2.left = mTmpRect.right - mBounds.bottom; mTmpRect2.right = mTmpRect2.left + mBounds.height(); mTmpRect2.bottom = mTmpRect2.top + mBounds.width(); break; } setBounds(mTmpRect2); } } boolean isFullscreen() { return mFullscreen; } /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen. * Returns true if something happened. */ boolean forceFullscreen(boolean forceFullscreen) { if (mForceFullscreen == forceFullscreen) { return false; } mForceFullscreen = forceFullscreen; if (forceFullscreen) { if (mFullscreen) { return false; } mPreForceFullscreenBounds.set(mBounds); return setBounds(null); } else { if (!mFullscreen || mPreForceFullscreenBounds.isEmpty()) { return false; } return setBounds(mPreForceFullscreenBounds); } } boolean isAnimating() { for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator; if (winAnimator.isAnimating() || winAnimator.mWin.mExiting) { return true; } } } } return false; } void addTask(Task task, boolean toTop) { addTask(task, toTop, task.showForAllUsers()); } /** * Put a Task in this stack. Used for adding and moving. * @param task The task to add. * @param toTop Whether to add it to the top or bottom. * @param showForAllUsers Whether to show the task regardless of the current user. */ void addTask(Task task, boolean toTop, boolean showForAllUsers) { int stackNdx; if (!toTop) { stackNdx = 0; } else { stackNdx = mTasks.size(); if (!showForAllUsers && !mService.isCurrentProfileLocked(task.mUserId)) { // Place the task below all current user tasks. while (--stackNdx >= 0) { final Task tmpTask = mTasks.get(stackNdx); if (!tmpTask.showForAllUsers() || !mService.isCurrentProfileLocked(tmpTask.mUserId)) { break; } } // Put it above first non-current user task. ++stackNdx; } } if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "addTask: task=" + task + " toTop=" + toTop + " pos=" + stackNdx); mTasks.add(stackNdx, task); task.mStack = this; if (toTop) { mDisplayContent.moveStack(this, true); } EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, stackNdx); } void moveTaskToTop(Task task) { if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers=" + Debug.getCallers(6)); mTasks.remove(task); addTask(task, true); } void moveTaskToBottom(Task task) { if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task); mTasks.remove(task); addTask(task, false); } /** * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the * back. * @param task The Task to delete. */ void removeTask(Task task) { if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task); mTasks.remove(task); if (mDisplayContent != null) { if (mTasks.isEmpty()) { mDisplayContent.moveStack(this, false); } mDisplayContent.layoutNeeded = true; } for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) { final AppWindowToken wtoken = mExitingAppTokens.get(appNdx); if (wtoken.mTask == task) { wtoken.mIsExiting = false; mExitingAppTokens.remove(appNdx); } } } void attachDisplayContent(DisplayContent displayContent) { if (mDisplayContent != null) { throw new IllegalStateException("attachDisplayContent: Already attached"); } mDisplayContent = displayContent; mDimLayer = new DimLayer(mService, this, displayContent); mAnimationBackgroundSurface = new DimLayer(mService, this, displayContent); updateDisplayInfo(); } void detachDisplay() { EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); boolean doAnotherLayoutPass = false; for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final AppTokenList appWindowTokens = mTasks.get(taskNdx).mAppTokens; for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) { final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows; for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) { mService.removeWindowInnerLocked(appWindows.get(winNdx)); doAnotherLayoutPass = true; } } } if (doAnotherLayoutPass) { mService.requestTraversalLocked(); } close(); } void resetAnimationBackgroundAnimator() { mAnimationBackgroundAnimator = null; mAnimationBackgroundSurface.hide(); } private long getDimBehindFadeDuration(long duration) { TypedValue tv = new TypedValue(); mService.mContext.getResources().getValue( com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); if (tv.type == TypedValue.TYPE_FRACTION) { duration = (long)tv.getFraction(duration, duration); } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { duration = tv.data; } return duration; } boolean animateDimLayers() { final int dimLayer; final float dimAmount; if (mDimWinAnimator == null) { dimLayer = mDimLayer.getLayer(); dimAmount = 0; } else { dimLayer = mDimWinAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM; dimAmount = mDimWinAnimator.mWin.mAttrs.dimAmount; } final float targetAlpha = mDimLayer.getTargetAlpha(); if (targetAlpha != dimAmount) { if (mDimWinAnimator == null) { mDimLayer.hide(DEFAULT_DIM_DURATION); } else { long duration = (mDimWinAnimator.mAnimating && mDimWinAnimator.mAnimation != null) ? mDimWinAnimator.mAnimation.computeDurationHint() : DEFAULT_DIM_DURATION; if (targetAlpha > dimAmount) { duration = getDimBehindFadeDuration(duration); } mDimLayer.show(dimLayer, dimAmount, duration); } } else if (mDimLayer.getLayer() != dimLayer) { mDimLayer.setLayer(dimLayer); } if (mDimLayer.isAnimating()) { if (!mService.okToDisplay()) { // Jump to the end of the animation. mDimLayer.show(); } else { return mDimLayer.stepAnimation(); } } return false; } void resetDimmingTag() { mDimmingTag = false; } void setDimmingTag() { mDimmingTag = true; } boolean testDimmingTag() { return mDimmingTag; } boolean isDimming() { return mDimLayer.isDimming(); } boolean isDimming(WindowStateAnimator winAnimator) { return mDimWinAnimator == winAnimator && mDimLayer.isDimming(); } void startDimmingIfNeeded(WindowStateAnimator newWinAnimator) { // Only set dim params on the highest dimmed layer. // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. if (newWinAnimator.mSurfaceShown && (mDimWinAnimator == null || !mDimWinAnimator.mSurfaceShown || mDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { mDimWinAnimator = newWinAnimator; if (mDimWinAnimator.mWin.mAppToken == null && !mFullscreen && mDisplayContent != null) { // Dim should cover the entire screen for system windows. mDisplayContent.getLogicalDisplayRect(mTmpRect); mDimLayer.setBounds(mTmpRect); } } } void stopDimmingIfNeeded() { if (!mDimmingTag && isDimming()) { mDimWinAnimator = null; mDimLayer.setBounds(mBounds); } } void setAnimationBackground(WindowStateAnimator winAnimator, int color) { int animLayer = winAnimator.mAnimLayer; if (mAnimationBackgroundAnimator == null || animLayer < mAnimationBackgroundAnimator.mAnimLayer) { mAnimationBackgroundAnimator = winAnimator; animLayer = mService.adjustAnimationBackground(winAnimator); mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM, ((color >> 24) & 0xff) / 255f, 0); } } void switchUser() { int top = mTasks.size(); for (int taskNdx = 0; taskNdx < top; ++taskNdx) { Task task = mTasks.get(taskNdx); if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) { mTasks.remove(taskNdx); mTasks.add(task); --top; } } } void close() { if (mAnimationBackgroundSurface != null) { mAnimationBackgroundSurface.destroySurface(); mAnimationBackgroundSurface = null; } if (mDimLayer != null) { mDimLayer.destroySurface(); mDimLayer = null; } mDisplayContent = null; } public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mStackId="); pw.println(mStackId); pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach); for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) { pw.print(prefix); pw.println(mTasks.get(taskNdx)); } if (mAnimationBackgroundSurface.isDimming()) { pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:"); mAnimationBackgroundSurface.printTo(prefix + " ", pw); } if (mDimLayer.isDimming()) { pw.print(prefix); pw.println("mDimLayer:"); mDimLayer.printTo(prefix + " ", pw); pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator); } if (!mExitingAppTokens.isEmpty()) { pw.println(); pw.println(" Exiting application tokens:"); for (int i=mExitingAppTokens.size()-1; i>=0; i--) { WindowToken token = mExitingAppTokens.get(i); pw.print(" Exiting App #"); pw.print(i); pw.print(' '); pw.print(token); pw.println(':'); token.dump(pw, " "); } } } @Override public String toString() { return "{stackId=" + mStackId + " tasks=" + mTasks + "}"; } }