| /* |
| * 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.graphics.Rect; |
| import android.os.Debug; |
| import android.util.EventLog; |
| import android.util.Slog; |
| import android.util.TypedValue; |
| 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(); |
| |
| /** 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; |
| |
| TaskStack(WindowManagerService service, int stackId) { |
| mService = service; |
| mStackId = stackId; |
| // 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 boolean underStatusBar = mBounds.top == 0; |
| |
| 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); |
| } |
| win.mUnderStatusBar = underStatusBar; |
| } |
| } |
| } |
| } |
| |
| boolean setBounds(Rect bounds) { |
| boolean oldFullscreen = mFullscreen; |
| if (mDisplayContent != null) { |
| mDisplayContent.getLogicalDisplayRect(mTmpRect); |
| mFullscreen = mTmpRect.equals(bounds); |
| } |
| |
| if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) { |
| return false; |
| } |
| |
| mDimLayer.setBounds(bounds, false); |
| mAnimationBackgroundSurface.setBounds(bounds, false); |
| mBounds.set(bounds); |
| |
| return true; |
| } |
| |
| void getBounds(Rect out) { |
| out.set(mBounds); |
| } |
| |
| void updateDisplayInfo() { |
| if (mFullscreen && mDisplayContent != null) { |
| mDisplayContent.getLogicalDisplayRect(mTmpRect); |
| setBounds(mTmpRect); |
| } |
| } |
| |
| boolean isFullscreen() { |
| return mFullscreen; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * 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. |
| */ |
| void addTask(Task task, boolean toTop) { |
| int stackNdx; |
| if (!toTop) { |
| stackNdx = 0; |
| } else { |
| stackNdx = mTasks.size(); |
| if (!mService.isCurrentProfileLocked(task.mUserId)) { |
| // Place the task below all current user tasks. |
| while (--stackNdx >= 0) { |
| if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).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.taskId, 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; |
| } |
| } |
| |
| 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(null, appWindows.get(winNdx)); |
| doAnotherLayoutPass = true; |
| } |
| } |
| } |
| if (doAnotherLayoutPass) { |
| mService.requestTraversalLocked(); |
| } |
| |
| mAnimationBackgroundSurface.destroySurface(); |
| mAnimationBackgroundSurface = null; |
| mDimLayer.destroySurface(); |
| mDimLayer = null; |
| mDisplayContent = null; |
| } |
| |
| 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. |
| final WindowStateAnimator existingDimWinAnimator = mDimWinAnimator; |
| // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. |
| if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null |
| || !existingDimWinAnimator.mSurfaceShown |
| || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { |
| mDimWinAnimator = newWinAnimator; |
| } |
| } |
| |
| void stopDimmingIfNeeded() { |
| if (!mDimmingTag && isDimming()) { |
| mDimWinAnimator = null; |
| } |
| } |
| |
| 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 userId) { |
| int top = mTasks.size(); |
| for (int taskNdx = 0; taskNdx < top; ++taskNdx) { |
| Task task = mTasks.get(taskNdx); |
| if (mService.isCurrentProfileLocked(task.mUserId)) { |
| mTasks.remove(taskNdx); |
| mTasks.add(task); |
| --top; |
| } |
| } |
| } |
| |
| void close() { |
| mDimLayer.mDimSurface.destroy(); |
| mAnimationBackgroundSurface.mDimSurface.destroy(); |
| } |
| |
| 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 + "}"; |
| } |
| } |