blob: b8db98b3c2f3e58ea8c2fb23f293fb91940c126a [file] [log] [blame]
/*
* Copyright (C) 2014 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.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import android.content.Context;
import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.server.AnimationThread;
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Singleton class that carries out the animations and Surface operations in a separate task
* on behalf of WindowManagerService.
*/
public class WindowAnimator {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowAnimator" : TAG_WM;
final WindowManagerService mService;
final Context mContext;
final WindowManagerPolicy mPolicy;
/** Is any window animating? */
private boolean mAnimating;
private boolean mLastRootAnimating;
final Choreographer.FrameCallback mAnimationFrameCallback;
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
int mBulkUpdateParams = 0;
Object mLastWindowFreezeSource;
SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
private boolean mInitialized = false;
// When set to true the animator will go over all windows after an animation frame is posted and
// check if some got replaced and can be removed.
private boolean mRemoveReplacedWindows = false;
private Choreographer mChoreographer;
/**
* Indicates whether we have an animation frame callback scheduled, which will happen at
* vsync-app and then schedule the animation tick at the right time (vsync-sf).
*/
private boolean mAnimationFrameCallbackScheduled;
/**
* A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is
* executed and the corresponding transaction is closed and applied.
*/
private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
private boolean mInExecuteAfterPrepareSurfacesRunnables;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
WindowAnimator(final WindowManagerService service) {
mService = service;
mContext = service.mContext;
mPolicy = service.mPolicy;
AnimationThread.getHandler().runWithScissors(
() -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
mAnimationFrameCallbackScheduled = false;
}
animate(frameTimeNs);
};
}
void addDisplayLocked(final int displayId) {
// Create the DisplayContentsAnimator object by retrieving it if the associated
// {@link DisplayContent} exists.
getDisplayContentsAnimatorLocked(displayId);
}
void removeDisplayLocked(final int displayId) {
final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
if (displayAnimator != null) {
if (displayAnimator.mScreenRotationAnimation != null) {
displayAnimator.mScreenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
}
}
mDisplayContentsAnimators.delete(displayId);
}
void ready() {
mInitialized = true;
}
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes
* an animation transaction, that might be blocking until the next sf-vsync, so we want to make
* sure other threads can make progress if this happens.
*/
private void animate(long frameTimeNs) {
synchronized (mService.mGlobalLock) {
if (!mInitialized) {
return;
}
// Schedule next frame already such that back-pressure happens continuously
scheduleAnimation();
}
synchronized (mService.mGlobalLock) {
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
mAnimating = false;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION animate");
mService.openSurfaceTransaction();
try {
final AccessibilityController accessibilityController =
mService.mAccessibilityController;
final int numDisplays = mDisplayContentsAnimators.size();
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final ScreenRotationAnimation screenRotationAnimation =
displayAnimator.mScreenRotationAnimation;
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
setAnimating(true);
} else {
mBulkUpdateParams |= SET_UPDATE_ROTATION;
screenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
// display.
if (accessibilityController != null) {
// We just finished rotation animation which means we did not
// announce the rotation and waited for it to end, announce now.
accessibilityController.onRotationChangedLocked(dc);
}
}
}
// Update animations of all applications, including those
// associated with exiting/removed apps
dc.updateWindowsForAnimator();
dc.updateBackgroundForAnimator();
dc.prepareSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
final int displayId = mDisplayContentsAnimators.keyAt(i);
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
dc.checkAppWindowsReadyToShow();
final ScreenRotationAnimation screenRotationAnimation =
mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
if (screenRotationAnimation != null) {
screenRotationAnimation.updateSurfaces(mTransaction);
}
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
if (accessibilityController != null) {
accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
}
}
if (!mAnimating) {
cancelAnimation();
}
if (mService.mWatermark != null) {
mService.mWatermark.drawIfNeeded();
}
SurfaceControl.mergeToGlobalTransaction(mTransaction);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mService.closeSurfaceTransaction("WindowAnimator");
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
boolean doRequest = false;
if (mBulkUpdateParams != 0) {
doRequest = mService.mRoot.copyAnimToLayoutParams();
}
if (hasPendingLayoutChanges || doRequest) {
mService.mWindowPlacerLocked.requestTraversal();
}
final boolean rootAnimating = mService.mRoot.isSelfOrChildAnimating();
if (rootAnimating && !mLastRootAnimating) {
// Usually app transitions but quite a load onto the system already (with all the
// things happening in app), so pause task snapshot persisting to not increase the
// load.
mService.mTaskSnapshotController.setPersisterPaused(true);
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
}
if (!rootAnimating && mLastRootAnimating) {
mService.mWindowPlacerLocked.requestTraversal();
mService.mTaskSnapshotController.setPersisterPaused(false);
Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
}
mLastRootAnimating = rootAnimating;
if (mRemoveReplacedWindows) {
mService.mRoot.removeReplacedWindows();
mRemoveReplacedWindows = false;
}
mService.destroyPreservedSurfaceLocked();
executeAfterPrepareSurfacesRunnables();
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
}
}
}
private static String bulkUpdateParamsToString(int bulkUpdateParams) {
StringBuilder builder = new StringBuilder(128);
if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
builder.append(" UPDATE_ROTATION");
}
if ((bulkUpdateParams & WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE) != 0) {
builder.append(" ORIENTATION_CHANGE_COMPLETE");
}
return builder.toString();
}
public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
final String subPrefix = " " + prefix;
final String subSubPrefix = " " + subPrefix;
for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
pw.print(prefix); pw.print("DisplayContentsAnimator #");
pw.print(mDisplayContentsAnimators.keyAt(i));
pw.println(":");
final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
final DisplayContent dc =
mService.mRoot.getDisplayContent(mDisplayContentsAnimators.keyAt(i));
dc.dumpWindowAnimators(pw, subPrefix);
if (displayAnimator.mScreenRotationAnimation != null) {
pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw);
} else if (dumpAll) {
pw.print(subPrefix); pw.println("no ScreenRotationAnimation ");
}
pw.println();
}
pw.println();
if (dumpAll) {
pw.print(prefix); pw.print("mCurrentTime=");
pw.println(TimeUtils.formatUptime(mCurrentTime));
}
if (mBulkUpdateParams != 0) {
pw.print(prefix); pw.print("mBulkUpdateParams=0x");
pw.print(Integer.toHexString(mBulkUpdateParams));
pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
}
}
int getPendingLayoutChanges(final int displayId) {
if (displayId < 0) {
return 0;
}
final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
}
void setPendingLayoutChanges(final int displayId, final int changes) {
if (displayId < 0) {
return;
}
final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
if (displayContent != null) {
displayContent.pendingLayoutChanges |= changes;
}
}
private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
if (displayId < 0) {
return null;
}
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
// It is possible that this underlying {@link DisplayContent} has been removed. In this
// case, we do not want to create an animator associated with it as {link #animate} will
// fail.
if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
displayAnimator = new DisplayContentsAnimator();
mDisplayContentsAnimators.put(displayId, displayAnimator);
}
return displayAnimator;
}
void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
if (animator != null) {
animator.mScreenRotationAnimation = animation;
}
}
ScreenRotationAnimation getScreenRotationAnimationLocked(int displayId) {
if (displayId < 0) {
return null;
}
DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
return animator != null? animator.mScreenRotationAnimation : null;
}
void requestRemovalOfReplacedWindows(WindowState win) {
mRemoveReplacedWindows = true;
}
void scheduleAnimation() {
if (!mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = true;
mChoreographer.postFrameCallback(mAnimationFrameCallback);
}
}
private void cancelAnimation() {
if (mAnimationFrameCallbackScheduled) {
mAnimationFrameCallbackScheduled = false;
mChoreographer.removeFrameCallback(mAnimationFrameCallback);
}
}
private class DisplayContentsAnimator {
ScreenRotationAnimation mScreenRotationAnimation = null;
}
boolean isAnimating() {
return mAnimating;
}
boolean isAnimationScheduled() {
return mAnimationFrameCallbackScheduled;
}
Choreographer getChoreographer() {
return mChoreographer;
}
void setAnimating(boolean animating) {
mAnimating = animating;
}
void orAnimating(boolean animating) {
mAnimating |= animating;
}
/**
* Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and
* the corresponding transaction is closed and applied.
*/
void addAfterPrepareSurfacesRunnable(Runnable r) {
// If runnables are already being handled in executeAfterPrepareSurfacesRunnable, then just
// immediately execute the runnable passed in.
if (mInExecuteAfterPrepareSurfacesRunnables) {
r.run();
return;
}
mAfterPrepareSurfacesRunnables.add(r);
scheduleAnimation();
}
void executeAfterPrepareSurfacesRunnables() {
// Don't even think about to start recursing!
if (mInExecuteAfterPrepareSurfacesRunnables) {
return;
}
mInExecuteAfterPrepareSurfacesRunnables = true;
// Traverse in order they were added.
final int size = mAfterPrepareSurfacesRunnables.size();
for (int i = 0; i < size; i++) {
mAfterPrepareSurfacesRunnables.get(i).run();
}
mAfterPrepareSurfacesRunnables.clear();
mInExecuteAfterPrepareSurfacesRunnables = false;
}
}