diff options
21 files changed, 1341 insertions, 89 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ab5fca2ce3c1..19fa7a3b0a2d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3253,6 +3253,9 @@ <!-- Feature flag to store TaskSnapshot in 16 bit pixel format to save memory. --> <bool name="config_use16BitTaskSnapshotPixelFormat">false</bool> + <!-- The amount to scale fullscreen activity snapshot for predict-back animation. --> + <item name="config_resActivitySnapshotScale" format="float" type="dimen">0.6</item> + <!-- Determines whether recent tasks are provided to the user. Default device has recents property. If this is false, then the following recents config flags are ignored. --> <bool name="config_hasRecents">true</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 60795adb03ad..251db6d87850 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -362,6 +362,7 @@ <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/> <java-symbol type="dimen" name="config_highResTaskSnapshotScale" /> <java-symbol type="dimen" name="config_lowResTaskSnapshotScale" /> + <java-symbol type="dimen" name="config_resActivitySnapshotScale" /> <java-symbol type="dimen" name="config_qsTileStrokeWidthInactive" /> <java-symbol type="dimen" name="config_qsTileStrokeWidthActive" /> <java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" /> diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index f215495d2f90..83804f75afea 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -55,8 +55,8 @@ import java.io.PrintWriter; * @param <CACHE> The basic cache for either Task or ActivityRecord */ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, - CACHE extends AbsAppSnapshotCache<TYPE>> { - private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotController" : TAG_WM; + CACHE extends SnapshotCache<TYPE>> { + static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotController" : TAG_WM; /** * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be * used as the snapshot. @@ -76,7 +76,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, static final int SNAPSHOT_MODE_NONE = 2; protected final WindowManagerService mService; - protected final float mHighResTaskSnapshotScale; + protected final float mHighResSnapshotScale; private final Rect mTmpRect = new Rect(); /** * Flag indicating whether we are running on an Android TV device. @@ -99,12 +99,13 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, PackageManager.FEATURE_LEANBACK); mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_EMBEDDED); - mHighResTaskSnapshotScale = initSnapshotScale(); + mHighResSnapshotScale = initSnapshotScale(); } protected float initSnapshotScale() { - return mService.mContext.getResources().getFloat( + final float config = mService.mContext.getResources().getFloat( com.android.internal.R.dimen.config_highResTaskSnapshotScale); + return Math.max(Math.min(config, 1f), 0.1f); } /** @@ -173,7 +174,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, final HardwareBuffer buffer = snapshot.getHardwareBuffer(); if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { buffer.close(); - Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" + Slog.e(TAG, "Invalid snapshot dimensions " + buffer.getWidth() + "x" + buffer.getHeight()); return null; } else { @@ -223,7 +224,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, Point taskSize = new Point(); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createSnapshot"); final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createSnapshot(source, - mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder); + mHighResSnapshotScale, builder.getPixelFormat(), taskSize, builder); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); builder.setTaskSize(taskSize); return taskSnapshot; @@ -397,11 +398,11 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription, - mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes()); + mHighResSnapshotScale, mainWindow.getRequestedVisibleTypes()); final int taskWidth = taskBounds.width(); final int taskHeight = taskBounds.height(); - final int width = (int) (taskWidth * mHighResTaskSnapshotScale); - final int height = (int) (taskHeight * mHighResTaskSnapshotScale); + final int width = (int) (taskWidth * mHighResSnapshotScale); + final int height = (int) (taskHeight * mHighResSnapshotScale); final RenderNode node = RenderNode.create("SnapshotController", null); node.setLeftTopRightBottom(0, 0, width, height); node.setClipToBounds(false); @@ -450,9 +451,28 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, return 0; } + /** + * Called when an {@link ActivityRecord} has been removed. + */ + void onAppRemoved(ActivityRecord activity) { + mCache.onAppRemoved(activity); + } + + /** + * Called when the process of an {@link ActivityRecord} has died. + */ + void onAppDied(ActivityRecord activity) { + mCache.onAppDied(activity); + } + + boolean isAnimatingByRecents(@NonNull Task task) { + return task.isAnimatingByRecents() + || mService.mAtmService.getTransitionController().inRecentsTransition(task); + } + void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale); - pw.println(prefix + "mTaskSnapshotEnabled=" + mSnapshotEnabled); + pw.println(prefix + "mHighResSnapshotScale=" + mHighResSnapshotScale); + pw.println(prefix + "mSnapshotEnabled=" + mSnapshotEnabled); mCache.dump(pw, prefix); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1e88ead64e51..d08293bf35b6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4223,7 +4223,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getDisplayContent().mOpeningApps.remove(this); getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this); - mWmService.mTaskSnapshotController.onAppRemoved(this); + mWmService.mSnapshotController.onAppRemoved(this); + mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); mTaskSupervisor.mStoppingActivities.remove(this); waitingToShow = false; @@ -5557,7 +5558,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && !fromTransition) { // Take the screenshot before possibly hiding the WSA, otherwise the screenshot // will not be taken. - mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible); + mWmService.mSnapshotController.notifyAppVisibilityChanged(this, visible); } // If we are hidden but there is no delay needed we immediately @@ -10465,6 +10466,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && !inPinnedWindowingMode() && !inFreeformWindowingMode(); } + boolean canCaptureSnapshot() { + if (!isSurfaceShowing() || findMainWindow() == null) { + return false; + } + return forAllWindows( + // Ensure at least one window for the top app is visible before attempting to + // take a screenshot. Visible here means that the WSA surface is shown and has + // an alpha greater than 0. + ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown() + && ws.mWinAnimator.mLastAlpha > 0f, true /* traverseTopToBottom */); + } + void overrideCustomTransition(boolean open, int enterAnim, int exitAnim, int backgroundColor) { CustomAppTransition transition = getCustomAnimation(open); if (transition == null) { diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java new file mode 100644 index 000000000000..a54dd826b5bb --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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 android.window.TaskSnapshot; + +/** + * A snapshot cache for activity, the token is the hashCode of the activity. + */ +class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { + + ActivitySnapshotCache(WindowManagerService service) { + super(service, "Activity"); + } + + @Override + void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { + final int hasCode = System.identityHashCode(ar); + final CacheEntry entry = mRunningCache.get(hasCode); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + } + mAppIdMap.put(ar, hasCode); + mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); + } +} diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java new file mode 100644 index 000000000000..90a4820e8724 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2022 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.SnapshotController.ACTIVITY_CLOSE; +import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN; +import static com.android.server.wm.SnapshotController.TASK_CLOSE; +import static com.android.server.wm.SnapshotController.TASK_OPEN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.os.Environment; +import android.os.SystemProperties; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.window.TaskSnapshot; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; +import com.android.server.wm.SnapshotController.TransitionState; + +import java.io.File; +import java.util.ArrayList; + +/** + * When an app token becomes invisible, we take a snapshot (bitmap) and put it into our cache. + * Internally we use gralloc buffers to be able to draw them wherever we like without any copying. + * <p> + * System applications may retrieve a snapshot to represent the current state of an activity, and + * draw them in their own process. + * <p> + * Unlike TaskSnapshotController, we only keep one activity snapshot for a visible task in the + * cache. Which should largely reduce the memory usage. + * <p> + * To access this class, acquire the global window manager lock. + */ +class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord, + ActivitySnapshotCache> { + private static final boolean DEBUG = false; + private static final String TAG = AbsAppSnapshotController.TAG; + // Maximum persisted snapshot count on disk. + private static final int MAX_PERSIST_SNAPSHOT_COUNT = 20; + + static final String SNAPSHOTS_DIRNAME = "activity_snapshots"; + + /** + * The pending activities which should capture snapshot when process transition finish. + */ + @VisibleForTesting + final ArraySet<ActivityRecord> mPendingCaptureActivity = new ArraySet<>(); + + /** + * The pending activities which should remove snapshot from memory when process transition + * finish. + */ + @VisibleForTesting + final ArraySet<ActivityRecord> mPendingRemoveActivity = new ArraySet<>(); + + /** + * The pending activities which should delete snapshot files when process transition finish. + */ + @VisibleForTesting + final ArraySet<ActivityRecord> mPendingDeleteActivity = new ArraySet<>(); + + /** + * The pending activities which should load snapshot from disk when process transition finish. + */ + @VisibleForTesting + final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>(); + + private final SnapshotPersistQueue mSnapshotPersistQueue; + private final PersistInfoProvider mPersistInfoProvider; + private final AppSnapshotLoader mSnapshotLoader; + + /** + * File information holders, to make the sequence align, always update status of + * mUserSavedFiles/mSavedFilesInOrder before persist file from mPersister. + */ + private final SparseArray<SparseArray<UserSavedFile>> mUserSavedFiles = new SparseArray<>(); + // Keep sorted with create timeline. + private final ArrayList<UserSavedFile> mSavedFilesInOrder = new ArrayList<>(); + private final TaskSnapshotPersister mPersister; + + ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) { + super(service); + mSnapshotPersistQueue = persistQueue; + mPersistInfoProvider = createPersistInfoProvider(service, + Environment::getDataSystemCeDirectory); + mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); + mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); + initialize(new ActivitySnapshotCache(service)); + + final boolean snapshotEnabled = + !service.mContext + .getResources() + .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots) + && isSnapshotEnabled() + && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go + setSnapshotEnabled(snapshotEnabled); + } + + void systemReady() { + if (shouldDisableSnapshots()) { + return; + } + mService.mSnapshotController.registerTransitionStateConsumer( + ACTIVITY_OPEN, this::handleOpenActivityTransition); + mService.mSnapshotController.registerTransitionStateConsumer( + ACTIVITY_CLOSE, this::handleCloseActivityTransition); + mService.mSnapshotController.registerTransitionStateConsumer( + TASK_OPEN, this::handleOpenTaskTransition); + mService.mSnapshotController.registerTransitionStateConsumer( + TASK_CLOSE, this::handleCloseTaskTransition); + } + + @Override + protected float initSnapshotScale() { + final float config = mService.mContext.getResources().getFloat( + com.android.internal.R.dimen.config_resActivitySnapshotScale); + return Math.max(Math.min(config, 1f), 0.1f); + } + + // TODO remove when enabled + static boolean isSnapshotEnabled() { + return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0; + } + + static PersistInfoProvider createPersistInfoProvider( + WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver) { + // Don't persist reduced file, instead we only persist the "HighRes" bitmap which has + // already scaled with #initSnapshotScale + final boolean use16BitFormat = service.mContext.getResources().getBoolean( + com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat); + return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME, + false /* enableLowResSnapshots */, 0 /* lowResScaleFactor */, use16BitFormat); + } + + /** Retrieves a snapshot for an activity from cache. */ + @Nullable + TaskSnapshot getSnapshot(ActivityRecord ar) { + final int code = getSystemHashCode(ar); + return mCache.getSnapshot(code); + } + + private void cleanUpUserFiles(int userId) { + synchronized (mSnapshotPersistQueue.getLock()) { + mSnapshotPersistQueue.sendToQueueLocked( + new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) { + @Override + boolean isReady() { + final UserManagerInternal mUserManagerInternal = + LocalServices.getService(UserManagerInternal.class); + return mUserManagerInternal.isUserUnlocked(userId); + } + + @Override + void write() { + final File file = mPersistInfoProvider.getDirectory(userId); + if (file.exists()) { + final File[] contents = file.listFiles(); + if (contents != null) { + for (int i = contents.length - 1; i >= 0; i--) { + contents[i].delete(); + } + } + } + } + }); + } + } + + /** + * Prepare to handle on transition start. Clear all temporary fields. + */ + void preTransitionStart() { + resetTmpFields(); + } + + /** + * on transition start has notified, start process data. + */ + void postTransitionStart() { + if (shouldDisableSnapshots()) { + return; + } + onCommitTransition(); + } + + @VisibleForTesting + void resetTmpFields() { + mPendingCaptureActivity.clear(); + mPendingRemoveActivity.clear(); + mPendingDeleteActivity.clear(); + mPendingLoadActivity.clear(); + } + + /** + * Start process all pending activities for a transition. + */ + private void onCommitTransition() { + if (DEBUG) { + Slog.d(TAG, "ActivitySnapshotController#onCommitTransition result:" + + " capture " + mPendingCaptureActivity + + " remove " + mPendingRemoveActivity + + " delete " + mPendingDeleteActivity + + " load " + mPendingLoadActivity); + } + // task snapshots + for (int i = mPendingCaptureActivity.size() - 1; i >= 0; i--) { + recordSnapshot(mPendingCaptureActivity.valueAt(i)); + } + // clear mTmpRemoveActivity from cache + for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) { + final ActivityRecord ar = mPendingRemoveActivity.valueAt(i); + final int code = getSystemHashCode(ar); + mCache.onIdRemoved(code); + } + // clear snapshot on cache and delete files + for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) { + final ActivityRecord ar = mPendingDeleteActivity.valueAt(i); + final int code = getSystemHashCode(ar); + mCache.onIdRemoved(code); + removeIfUserSavedFileExist(code, ar.mUserId); + } + // load snapshot to cache + for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) { + final ActivityRecord ar = mPendingLoadActivity.valueAt(i); + final int code = getSystemHashCode(ar); + final int userId = ar.mUserId; + if (mCache.getSnapshot(code) != null) { + // already in cache, skip + continue; + } + if (containsFile(code, userId)) { + synchronized (mSnapshotPersistQueue.getLock()) { + mSnapshotPersistQueue.sendToQueueLocked( + new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) { + @Override + void write() { + final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code, + userId, false /* loadLowResolutionBitmap */); + synchronized (mService.getWindowManagerLock()) { + if (snapshot != null && !ar.finishing) { + mCache.putSnapshot(ar, snapshot); + } + } + } + }); + } + } + } + // don't keep any reference + resetTmpFields(); + } + + private void recordSnapshot(ActivityRecord activity) { + final TaskSnapshot snapshot = recordSnapshotInner(activity, false /* allowSnapshotHome */); + if (snapshot != null) { + final int code = getSystemHashCode(activity); + addUserSavedFile(code, activity.mUserId, snapshot); + } + } + + /** + * Called when the visibility of an app changes outside the regular app transition flow. + */ + void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) { + if (!visible) { + resetTmpFields(); + addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity, + "remove-snapshot"); + onCommitTransition(); + } + } + + private static int getSystemHashCode(ActivityRecord activity) { + return System.identityHashCode(activity); + } + + void handleOpenActivityTransition(TransitionState<ActivityRecord> transitionState) { + ArraySet<ActivityRecord> participant = transitionState.getParticipant(false /* open */); + for (ActivityRecord ar : participant) { + mPendingCaptureActivity.add(ar); + // remove the snapshot for the one below close + final ActivityRecord below = ar.getTask().getActivityBelow(ar); + if (below != null) { + mPendingRemoveActivity.add(below); + } + } + } + + void handleCloseActivityTransition(TransitionState<ActivityRecord> transitionState) { + ArraySet<ActivityRecord> participant = transitionState.getParticipant(true /* open */); + for (ActivityRecord ar : participant) { + mPendingDeleteActivity.add(ar); + // load next one if exists. + final ActivityRecord below = ar.getTask().getActivityBelow(ar); + if (below != null) { + mPendingLoadActivity.add(below); + } + } + } + + void handleCloseTaskTransition(TransitionState<Task> closeTaskTransitionRecord) { + ArraySet<Task> participant = closeTaskTransitionRecord.getParticipant(false /* open */); + for (Task close : participant) { + // this is close task transition + // remove the N - 1 from cache + addBelowTopActivityIfExist(close, mPendingRemoveActivity, "remove-snapshot"); + } + } + + void handleOpenTaskTransition(TransitionState<Task> openTaskTransitionRecord) { + ArraySet<Task> participant = openTaskTransitionRecord.getParticipant(true /* open */); + for (Task open : participant) { + // this is close task transition + // remove the N - 1 from cache + addBelowTopActivityIfExist(open, mPendingLoadActivity, "load-snapshot"); + // Move the activities to top of mSavedFilesInOrder, so when purge happen, there + // will trim the persisted files from the most non-accessed. + adjustSavedFileOrder(open); + } + } + + // Add the top -1 activity to a set if it exists. + private void addBelowTopActivityIfExist(Task task, ArraySet<ActivityRecord> set, + String debugMessage) { + final ActivityRecord topActivity = task.getTopMostActivity(); + if (topActivity != null) { + final ActivityRecord below = task.getActivityBelow(topActivity); + if (below != null) { + set.add(below); + if (DEBUG) { + Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist " + + below + " from " + debugMessage); + } + } + } + } + + private void adjustSavedFileOrder(Task nextTopTask) { + final int userId = nextTopTask.mUserId; + nextTopTask.forAllActivities(ar -> { + final int code = getSystemHashCode(ar); + final UserSavedFile usf = getUserFiles(userId).get(code); + if (usf != null) { + mSavedFilesInOrder.remove(usf); + mSavedFilesInOrder.add(usf); + } + }, false /* traverseTopToBottom */); + } + + @Override + void onAppRemoved(ActivityRecord activity) { + super.onAppRemoved(activity); + final int code = getSystemHashCode(activity); + removeIfUserSavedFileExist(code, activity.mUserId); + if (DEBUG) { + Slog.d(TAG, "ActivitySnapshotController#onAppRemoved delete snapshot " + activity); + } + } + + @Override + void onAppDied(ActivityRecord activity) { + super.onAppDied(activity); + final int code = getSystemHashCode(activity); + removeIfUserSavedFileExist(code, activity.mUserId); + if (DEBUG) { + Slog.d(TAG, "ActivitySnapshotController#onAppDied delete snapshot " + activity); + } + } + + @Override + ActivityRecord getTopActivity(ActivityRecord activity) { + return activity; + } + + @Override + ActivityRecord getTopFullscreenActivity(ActivityRecord activity) { + final WindowState win = activity.findMainWindow(); + return (win != null && win.mAttrs.isFullscreen()) ? activity : null; + } + + @Override + ActivityManager.TaskDescription getTaskDescription(ActivityRecord object) { + return object.taskDescription; + } + + /** + * Find the window for a given activity to take a snapshot. During app transitions, trampoline + * activities can appear in the children, but should be ignored. + */ + @Override + protected ActivityRecord findAppTokenForSnapshot(ActivityRecord activity) { + if (activity == null) { + return null; + } + return activity.canCaptureSnapshot() ? activity : null; + } + + @Override + protected boolean use16BitFormat() { + return mPersistInfoProvider.use16BitFormat(); + } + + @NonNull + private SparseArray<UserSavedFile> getUserFiles(int userId) { + if (mUserSavedFiles.get(userId) == null) { + mUserSavedFiles.put(userId, new SparseArray<>()); + // This is the first time this user attempt to access snapshot, clear up the disk. + cleanUpUserFiles(userId); + } + return mUserSavedFiles.get(userId); + } + + private void removeIfUserSavedFileExist(int code, int userId) { + final UserSavedFile usf = getUserFiles(userId).get(code); + if (usf != null) { + mUserSavedFiles.remove(code); + mSavedFilesInOrder.remove(usf); + mPersister.removeSnap(code, userId); + } + } + + private boolean containsFile(int code, int userId) { + return getUserFiles(userId).get(code) != null; + } + + private void addUserSavedFile(int code, int userId, TaskSnapshot snapshot) { + final SparseArray<UserSavedFile> savedFiles = getUserFiles(userId); + final UserSavedFile savedFile = savedFiles.get(code); + if (savedFile == null) { + final UserSavedFile usf = new UserSavedFile(code, userId); + savedFiles.put(code, usf); + mSavedFilesInOrder.add(usf); + mPersister.persistSnapshot(code, userId, snapshot); + + if (mSavedFilesInOrder.size() > MAX_PERSIST_SNAPSHOT_COUNT * 2) { + purgeSavedFile(); + } + } + } + + private void purgeSavedFile() { + final int savedFileCount = mSavedFilesInOrder.size(); + final int removeCount = savedFileCount - MAX_PERSIST_SNAPSHOT_COUNT; + final ArrayList<UserSavedFile> usfs = new ArrayList<>(); + if (removeCount > 0) { + final int removeTillIndex = savedFileCount - removeCount; + for (int i = savedFileCount - 1; i > removeTillIndex; --i) { + final UserSavedFile usf = mSavedFilesInOrder.remove(i); + if (usf != null) { + mUserSavedFiles.remove(usf.mFileId); + usfs.add(usf); + } + } + } + if (usfs.size() > 0) { + removeSnapshotFiles(usfs); + } + } + + private void removeSnapshotFiles(ArrayList<UserSavedFile> files) { + synchronized (mSnapshotPersistQueue.getLock()) { + mSnapshotPersistQueue.sendToQueueLocked( + new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) { + @Override + void write() { + for (int i = files.size() - 1; i >= 0; --i) { + final UserSavedFile usf = files.get(i); + mSnapshotPersistQueue.deleteSnapshot( + usf.mFileId, usf.mUserId, mPersistInfoProvider); + } + } + }); + } + } + + static class UserSavedFile { + int mFileId; + int mUserId; + UserSavedFile(int fileId, int userId) { + mFileId = fileId; + mUserId = userId; + } + } +} diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 4e94f96af37b..841d28b0231f 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -321,7 +321,7 @@ public class AppTransitionController { mService.mSurfaceAnimationRunner.continueStartingAnimations(); } - mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent); + mService.mSnapshotController.onTransitionStarting(mDisplayContent); mDisplayContent.mOpeningApps.clear(); mDisplayContent.mClosingApps.clear(); diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index c8adc8f8b937..401b2604c28f 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -25,13 +25,13 @@ import java.io.PrintWriter; * Base class for an app snapshot cache * @param <TYPE> The basic type, either Task or ActivityRecord */ -abstract class AbsAppSnapshotCache<TYPE extends WindowContainer> { +abstract class SnapshotCache<TYPE extends WindowContainer> { protected final WindowManagerService mService; protected final String mName; protected final ArrayMap<ActivityRecord, Integer> mAppIdMap = new ArrayMap<>(); protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>(); - AbsAppSnapshotCache(WindowManagerService service, String name) { + SnapshotCache(WindowManagerService service, String name) { mService = service; mName = name; } diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java new file mode 100644 index 000000000000..cd1263e5d168 --- /dev/null +++ b/services/core/java/com/android/server/wm/SnapshotController.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2022 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 android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.annotation.IntDef; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.view.WindowManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Integrates common functionality from TaskSnapshotController and ActivitySnapshotController. + */ +class SnapshotController { + private static final boolean DEBUG = false; + private static final String TAG = AbsAppSnapshotController.TAG; + + static final int ACTIVITY_OPEN = 1; + static final int ACTIVITY_CLOSE = 2; + static final int TASK_OPEN = 4; + static final int TASK_CLOSE = 8; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = {ACTIVITY_OPEN, + ACTIVITY_CLOSE, + TASK_OPEN, + TASK_CLOSE}) + @interface TransitionStateType {} + + private final SnapshotPersistQueue mSnapshotPersistQueue; + final TaskSnapshotController mTaskSnapshotController; + final ActivitySnapshotController mActivitySnapshotController; + + private final ArraySet<Task> mTmpCloseTasks = new ArraySet<>(); + private final ArraySet<Task> mTmpOpenTasks = new ArraySet<>(); + + private final SparseArray<TransitionState> mTmpOpenCloseRecord = new SparseArray<>(); + private final ArraySet<Integer> mTmpAnalysisRecord = new ArraySet<>(); + private final SparseArray<ArrayList<Consumer<TransitionState>>> mTransitionStateConsumer = + new SparseArray<>(); + private int mActivatedType; + + private final ActivityOrderCheck mActivityOrderCheck = new ActivityOrderCheck(); + private final ActivityOrderCheck.AnalysisResult mResultHandler = (type, close, open) -> { + addTransitionRecord(type, true/* open */, open); + addTransitionRecord(type, false/* open */, close); + }; + + private static class ActivityOrderCheck { + private ActivityRecord mOpenActivity; + private ActivityRecord mCloseActivity; + private int mOpenIndex = -1; + private int mCloseIndex = -1; + + private void reset() { + mOpenActivity = null; + mCloseActivity = null; + mOpenIndex = -1; + mCloseIndex = -1; + } + + private void setTarget(boolean open, ActivityRecord ar, int index) { + if (open) { + mOpenActivity = ar; + mOpenIndex = index; + } else { + mCloseActivity = ar; + mCloseIndex = index; + } + } + + void analysisOrder(ArraySet<ActivityRecord> closeApps, + ArraySet<ActivityRecord> openApps, Task task, AnalysisResult result) { + for (int j = closeApps.size() - 1; j >= 0; j--) { + final ActivityRecord ar = closeApps.valueAt(j); + if (ar.getTask() == task) { + setTarget(false, ar, task.mChildren.indexOf(ar)); + break; + } + } + for (int j = openApps.size() - 1; j >= 0; j--) { + final ActivityRecord ar = openApps.valueAt(j); + if (ar.getTask() == task) { + setTarget(true, ar, task.mChildren.indexOf(ar)); + break; + } + } + if (mOpenIndex > mCloseIndex && mCloseIndex != -1) { + result.onCheckResult(ACTIVITY_OPEN, mCloseActivity, mOpenActivity); + } else if (mOpenIndex < mCloseIndex && mOpenIndex != -1) { + result.onCheckResult(ACTIVITY_CLOSE, mCloseActivity, mOpenActivity); + } + reset(); + } + private interface AnalysisResult { + void onCheckResult(@TransitionStateType int type, + ActivityRecord close, ActivityRecord open); + } + } + + private void addTransitionRecord(int type, boolean open, WindowContainer target) { + TransitionState record = mTmpOpenCloseRecord.get(type); + if (record == null) { + record = new TransitionState(); + mTmpOpenCloseRecord.set(type, record); + } + record.addParticipant(target, open); + mTmpAnalysisRecord.add(type); + } + + private void clearRecord() { + mTmpOpenCloseRecord.clear(); + mTmpAnalysisRecord.clear(); + } + + static class TransitionState<TYPE extends WindowContainer> { + private final ArraySet<TYPE> mOpenParticipant = new ArraySet<>(); + private final ArraySet<TYPE> mCloseParticipant = new ArraySet<>(); + + void addParticipant(TYPE target, boolean open) { + final ArraySet<TYPE> participant = open + ? mOpenParticipant : mCloseParticipant; + participant.add(target); + } + + ArraySet<TYPE> getParticipant(boolean open) { + return open ? mOpenParticipant : mCloseParticipant; + } + } + + SnapshotController(WindowManagerService wms) { + mSnapshotPersistQueue = new SnapshotPersistQueue(); + mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue); + mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue); + } + + void registerTransitionStateConsumer(@TransitionStateType int type, + Consumer<TransitionState> consumer) { + ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type); + if (consumers == null) { + consumers = new ArrayList<>(); + mTransitionStateConsumer.set(type, consumers); + } + if (!consumers.contains(consumer)) { + consumers.add(consumer); + } + mActivatedType |= type; + } + + void unregisterTransitionStateConsumer(int type, Consumer<TransitionState> consumer) { + final ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type); + if (consumers == null) { + return; + } + consumers.remove(consumer); + if (consumers.size() == 0) { + mActivatedType &= ~type; + } + } + + private boolean hasTransitionStateConsumer(@TransitionStateType int type) { + return (mActivatedType & type) != 0; + } + + void systemReady() { + mSnapshotPersistQueue.systemReady(); + mTaskSnapshotController.systemReady(); + mActivitySnapshotController.systemReady(); + } + + void setPause(boolean paused) { + mSnapshotPersistQueue.setPaused(paused); + } + + void onAppRemoved(ActivityRecord activity) { + mTaskSnapshotController.onAppRemoved(activity); + mActivitySnapshotController.onAppRemoved(activity); + } + + void onAppDied(ActivityRecord activity) { + mTaskSnapshotController.onAppDied(activity); + mActivitySnapshotController.onAppDied(activity); + } + + void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) { + if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) { + // close task transition + addTransitionRecord(TASK_CLOSE, false /*open*/, appWindowToken.getTask()); + mActivitySnapshotController.preTransitionStart(); + notifyTransition(TASK_CLOSE); + mActivitySnapshotController.postTransitionStart(); + clearRecord(); + } + } + + // For legacy transition + void onTransitionStarting(DisplayContent displayContent) { + handleAppTransition(displayContent.mClosingApps, displayContent.mOpeningApps); + } + + // For shell transition, adapt to legacy transition. + void onTransitionReady(@WindowManager.TransitionType int type, + ArraySet<WindowContainer> participants) { + final boolean isTransitionOpen = isTransitionOpen(type); + final boolean isTransitionClose = isTransitionClose(type); + if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM + || (mActivatedType == 0)) { + return; + } + final ArraySet<ActivityRecord> openingApps = new ArraySet<>(); + final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); + + for (int i = participants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = participants.valueAt(i).asActivityRecord(); + if (ar == null || ar.getTask() == null) continue; + if (ar.isVisibleRequested()) { + openingApps.add(ar); + } else { + closingApps.add(ar); + } + } + handleAppTransition(closingApps, openingApps); + } + + private static boolean isTransitionOpen(int type) { + return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT; + } + private static boolean isTransitionClose(int type) { + return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; + } + + @VisibleForTesting + void handleAppTransition(ArraySet<ActivityRecord> closingApps, + ArraySet<ActivityRecord> openApps) { + if (mActivatedType == 0) { + return; + } + analysisTransition(closingApps, openApps); + mActivitySnapshotController.preTransitionStart(); + for (Integer transitionType : mTmpAnalysisRecord) { + notifyTransition(transitionType); + } + mActivitySnapshotController.postTransitionStart(); + clearRecord(); + } + + private void notifyTransition(int transitionType) { + final TransitionState record = mTmpOpenCloseRecord.get(transitionType); + final ArrayList<Consumer<TransitionState>> consumers = + mTransitionStateConsumer.get(transitionType); + for (Consumer<TransitionState> consumer : consumers) { + consumer.accept(record); + } + } + + private void analysisTransition(ArraySet<ActivityRecord> closingApps, + ArraySet<ActivityRecord> openingApps) { + getParticipantTasks(closingApps, mTmpCloseTasks, false /* isOpen */); + getParticipantTasks(openingApps, mTmpOpenTasks, true /* isOpen */); + if (DEBUG) { + Slog.d(TAG, "AppSnapshotController#analysisTransition participants" + + " mTmpCloseTasks " + mTmpCloseTasks + + " mTmpOpenTasks " + mTmpOpenTasks); + } + for (int i = mTmpCloseTasks.size() - 1; i >= 0; i--) { + final Task closeTask = mTmpCloseTasks.valueAt(i); + if (mTmpOpenTasks.contains(closeTask)) { + if (hasTransitionStateConsumer(ACTIVITY_OPEN) + || hasTransitionStateConsumer(ACTIVITY_CLOSE)) { + mActivityOrderCheck.analysisOrder(closingApps, openingApps, closeTask, + mResultHandler); + } + } else if (hasTransitionStateConsumer(TASK_CLOSE)) { + // close task transition + addTransitionRecord(TASK_CLOSE, false /*open*/, closeTask); + } + } + if (hasTransitionStateConsumer(TASK_OPEN)) { + for (int i = mTmpOpenTasks.size() - 1; i >= 0; i--) { + final Task openTask = mTmpOpenTasks.valueAt(i); + if (!mTmpCloseTasks.contains(openTask)) { + // this is open task transition + addTransitionRecord(TASK_OPEN, true /*open*/, openTask); + } + } + } + mTmpCloseTasks.clear(); + mTmpOpenTasks.clear(); + } + + private void getParticipantTasks(ArraySet<ActivityRecord> activityRecords, ArraySet<Task> tasks, + boolean isOpen) { + for (int i = activityRecords.size() - 1; i >= 0; i--) { + final ActivityRecord activity = activityRecords.valueAt(i); + final Task task = activity.getTask(); + if (task == null) continue; + + if (isOpen == activity.isVisibleRequested()) { + tasks.add(task); + } + } + } + + void dump(PrintWriter pw, String prefix) { + mTaskSnapshotController.dump(pw, prefix); + mActivitySnapshotController.dump(pw, prefix); + } +} diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index fdc361671b8d..afef85e34fe0 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -129,7 +129,7 @@ class SnapshotPersistQueue { } } - private void deleteSnapshot(int index, int userId, PersistInfoProvider provider) { + void deleteSnapshot(int index, int userId, PersistInfoProvider provider) { final File protoFile = provider.getProtoFile(index, userId); final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId); protoFile.delete(); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index 55e863ea5fbd..33486ccb995f 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -24,7 +24,7 @@ import android.window.TaskSnapshot; * <p> * Access to this class should be guarded by the global window manager lock. */ -class TaskSnapshotCache extends AbsAppSnapshotCache<Task> { +class TaskSnapshotCache extends SnapshotCache<Task> { private final AppSnapshotLoader mLoader; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 679f0f592035..4d0bff961e60 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.server.wm.SnapshotController.TASK_CLOSE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -77,6 +78,13 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot setSnapshotEnabled(snapshotEnabled); } + void systemReady() { + if (!shouldDisableSnapshots()) { + mService.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE, + this::handleTaskClose); + } + } + static PersistInfoProvider createPersistInfoProvider(WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver) { final float highResTaskSnapshotScale = service.mContext.getResources().getFloat( @@ -109,8 +117,21 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot enableLowResSnapshots, lowResScaleFactor, use16BitFormat); } - void onTransitionStarting(DisplayContent displayContent) { - handleClosingApps(displayContent.mClosingApps); + void handleTaskClose(SnapshotController.TransitionState<Task> closeTaskTransitionRecord) { + if (shouldDisableSnapshots()) { + return; + } + mTmpTasks.clear(); + final ArraySet<Task> tasks = closeTaskTransitionRecord.getParticipant(false /* open */); + if (mService.mAtmService.getTransitionController().isShellTransitionsEnabled()) { + mTmpTasks.addAll(tasks); + } else { + for (Task task : tasks) { + getClosingTasksInner(task, mTmpTasks); + } + } + snapshotTasks(mTmpTasks); + mSkipClosingAppSnapshotTasks.clear(); } /** @@ -189,18 +210,7 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot * children, which should be ignored. */ @Nullable protected ActivityRecord findAppTokenForSnapshot(Task task) { - return task.getActivity((r) -> { - if (r == null || !r.isSurfaceShowing() || r.findMainWindow() == null) { - return false; - } - return r.forAllWindows( - // Ensure at least one window for the top app is visible before attempting to - // take a screenshot. Visible here means that the WSA surface is shown and has - // an alpha greater than 0. - ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown() - && ws.mWinAnimator.mLastAlpha > 0f, true /* traverseTopToBottom */); - - }); + return task.getActivity(ActivityRecord::canCaptureSnapshot); } @@ -272,32 +282,22 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot final Task task = activity.getTask(); if (task == null) continue; - // Since RecentsAnimation will handle task snapshot while switching apps with the - // best capture timing (e.g. IME window capture), - // No need additional task capture while task is controlled by RecentsAnimation. - if (isAnimatingByRecents(task)) { - mSkipClosingAppSnapshotTasks.add(task); - } - // If the task of the app is not visible anymore, it means no other app in that task - // is opening. Thus, the task is closing. - if (!task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) { - outClosingTasks.add(task); - } + getClosingTasksInner(task, outClosingTasks); } } - /** - * Called when an {@link ActivityRecord} has been removed. - */ - void onAppRemoved(ActivityRecord activity) { - mCache.onAppRemoved(activity); - } - - /** - * Called when the process of an {@link ActivityRecord} has died. - */ - void onAppDied(ActivityRecord activity) { - mCache.onAppDied(activity); + void getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks) { + // Since RecentsAnimation will handle task snapshot while switching apps with the + // best capture timing (e.g. IME window capture), + // No need additional task capture while task is controlled by RecentsAnimation. + if (isAnimatingByRecents(task)) { + mSkipClosingAppSnapshotTasks.add(task); + } + // If the task of the app is not visible anymore, it means no other app in that task + // is opening. Thus, the task is closing. + if (!task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) { + outClosingTasks.add(task); + } } void notifyTaskRemovedFromRecents(int taskId, int userId) { @@ -361,9 +361,4 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId); snapshotTasks(mTmpTasks, allowSnapshotHome); } - - private boolean isAnimatingByRecents(@NonNull Task task) { - return task.isAnimatingByRecents() - || mService.mAtmService.getTransitionController().inRecentsTransition(task); - } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 873a83d5527e..362e1c803e46 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -879,8 +879,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { && mTransientLaunches != null) { // If transition is transient, then snapshots are taken at end of // transition. - mController.mTaskSnapshotController.recordSnapshot( - task, false /* allowSnapshotHome */); + mController.mSnapshotController.mTaskSnapshotController + .recordSnapshot(task, false /* allowSnapshotHome */); + mController.mSnapshotController.mActivitySnapshotController + .notifyAppVisibilityChanged(ar, false /* visible */); } ar.commitVisibility(false /* visible */, false /* performLayout */, true /* fromTransition */); @@ -1225,13 +1227,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // transferred. If transition is transient, IME won't be moved during the transition and // the tasks are still live, so we take the snapshot at the end of the transition instead. if (mTransientLaunches == null) { - for (int i = mParticipants.size() - 1; i >= 0; --i) { - final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar == null || ar.isVisibleRequested() || ar.getTask() == null - || ar.getTask().isVisibleRequested()) continue; - mController.mTaskSnapshotController.recordSnapshot( - ar.getTask(), false /* allowSnapshotHome */); - } + mController.mSnapshotController.onTransitionReady(mType, mParticipants); } // This is non-null only if display has changes. It handles the visible windows that don't diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index c74f167c2222..f314b21a0d72 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -88,8 +88,9 @@ class TransitionController { private WindowProcessController mTransitionPlayerProc; final ActivityTaskManagerService mAtm; + final RemotePlayer mRemotePlayer; - TaskSnapshotController mTaskSnapshotController; + SnapshotController mSnapshotController; TransitionTracer mTransitionTracer; private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = @@ -153,7 +154,7 @@ class TransitionController { } void setWindowManager(WindowManagerService wms) { - mTaskSnapshotController = wms.mTaskSnapshotController; + mSnapshotController = wms.mSnapshotController; mTransitionTracer = wms.mTransitionTracer; mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); @@ -739,12 +740,12 @@ class TransitionController { t.setEarlyWakeupStart(); // Usually transitions put quite a load onto the system already (with all the things // happening in app), so pause task snapshot persisting to not increase the load. - mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(true); + mAtm.mWindowManager.mSnapshotController.setPause(true); mAnimatingState = true; Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); - mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(false); + mAtm.mWindowManager.mSnapshotController.setPause(false); mAnimatingState = false; Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 25965331241e..10bedd4b921f 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -200,11 +200,11 @@ public class WindowAnimator { | ANIMATION_TYPE_RECENTS /* typesToCheck */); if (runningExpensiveAnimations && !mRunningExpensiveAnimations) { // Usually app transitions put 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.mSnapshotPersistQueue.setPaused(true); + // happening in app), so pause snapshot persisting to not increase the load. + mService.mSnapshotController.setPause(true); mTransaction.setEarlyWakeupStart(); } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) { - mService.mSnapshotPersistQueue.setPaused(false); + mService.mSnapshotController.setPause(false); mTransaction.setEarlyWakeupEnd(); } mRunningExpensiveAnimations = runningExpensiveAnimations; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f7641f5bfb54..918729d114d3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -689,8 +689,8 @@ public class WindowManagerService extends IWindowManager.Stub // changes the orientation. private final PowerManager.WakeLock mScreenFrozenLock; - final SnapshotPersistQueue mSnapshotPersistQueue; final TaskSnapshotController mTaskSnapshotController; + final SnapshotController mSnapshotController; final BlurController mBlurController; final TaskFpsCallbackController mTaskFpsCallbackController; @@ -1200,8 +1200,8 @@ public class WindowManagerService extends IWindowManager.Stub mSyncEngine = new BLASTSyncEngine(this); mWindowPlacerLocked = new WindowSurfacePlacer(this); - mSnapshotPersistQueue = new SnapshotPersistQueue(); - mTaskSnapshotController = new TaskSnapshotController(this, mSnapshotPersistQueue); + mSnapshotController = new SnapshotController(this); + mTaskSnapshotController = mSnapshotController.mTaskSnapshotController; mWindowTracing = WindowTracing.createDefaultAndStartLooper(this, Choreographer.getInstance()); @@ -5141,7 +5141,7 @@ public class WindowManagerService extends IWindowManager.Stub mSystemReady = true; mPolicy.systemReady(); mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady); - mSnapshotPersistQueue.systemReady(); + mSnapshotController.systemReady(); mHasWideColorGamutSupport = queryWideColorGamutSupport(); mHasHdrSupport = queryHdrSupport(); UiThread.getHandler().post(mSettingsObserver::loadSettings); @@ -6685,7 +6685,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); mInputManagerCallback.dump(pw, " "); - mTaskSnapshotController.dump(pw, " "); + mSnapshotController.dump(pw, " "); if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.dump(pw, " "); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 8a083aa6220f..d1bd06f7fa99 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2913,8 +2913,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP .windowForClientLocked(mSession, mClient, false); Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { - if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) { - mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord); + if (win.mActivityRecord != null + && win.mActivityRecord.findMainWindow() == win) { + mWmService.mSnapshotController.onAppDied(win.mActivityRecord); } win.removeIfPossible(); } else if (mHasSurface) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java new file mode 100644 index 000000000000..0eca8c988575 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 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 android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link ActivitySnapshotController}. + * + * Build/Install/Run: + * * atest WmTests:ActivitySnapshotControllerTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class ActivitySnapshotControllerTests extends WindowTestsBase { + + private ActivitySnapshotController mActivitySnapshotController; + @Before + public void setUp() throws Exception { + mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController; + mActivitySnapshotController.resetTmpFields(); + } + @Test + public void testOpenActivityTransition() { + final SnapshotController.TransitionState transitionState = + new SnapshotController.TransitionState(); + final Task task = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState openingWindow = createAppWindow(task, + ACTIVITY_TYPE_STANDARD, "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + transitionState.addParticipant(closingWindow.mActivityRecord, false); + transitionState.addParticipant(openingWindow.mActivityRecord, true); + mActivitySnapshotController.handleOpenActivityTransition(transitionState); + + assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size()); + assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size()); + assertEquals(closingWindow.mActivityRecord, + mActivitySnapshotController.mPendingCaptureActivity.valueAt(0)); + mActivitySnapshotController.resetTmpFields(); + + // simulate three activity + final WindowState belowClose = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "belowClose"); + belowClose.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + mActivitySnapshotController.handleOpenActivityTransition(transitionState); + assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size()); + assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size()); + assertEquals(closingWindow.mActivityRecord, + mActivitySnapshotController.mPendingCaptureActivity.valueAt(0)); + assertEquals(belowClose.mActivityRecord, + mActivitySnapshotController.mPendingRemoveActivity.valueAt(0)); + } + + @Test + public void testCloseActivityTransition() { + final SnapshotController.TransitionState transitionState = + new SnapshotController.TransitionState(); + final Task task = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + final WindowState openingWindow = createAppWindow(task, + ACTIVITY_TYPE_STANDARD, "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + transitionState.addParticipant(closingWindow.mActivityRecord, false); + transitionState.addParticipant(openingWindow.mActivityRecord, true); + mActivitySnapshotController.handleCloseActivityTransition(transitionState); + assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size()); + assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size()); + assertEquals(openingWindow.mActivityRecord, + mActivitySnapshotController.mPendingDeleteActivity.valueAt(0)); + mActivitySnapshotController.resetTmpFields(); + + // simulate three activity + final WindowState belowOpen = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "belowOpen"); + belowOpen.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + mActivitySnapshotController.handleCloseActivityTransition(transitionState); + assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size()); + assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size()); + assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size()); + assertEquals(openingWindow.mActivityRecord, + mActivitySnapshotController.mPendingDeleteActivity.valueAt(0)); + assertEquals(belowOpen.mActivityRecord, + mActivitySnapshotController.mPendingLoadActivity.valueAt(0)); + } + + @Test + public void testTaskTransition() { + final SnapshotController.TransitionState taskCloseTransition = + new SnapshotController.TransitionState(); + final SnapshotController.TransitionState taskOpenTransition = + new SnapshotController.TransitionState(); + final Task closeTask = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD, + "closingWindowBelow"); + closingWindowBelow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + + final Task openTask = createTask(mDisplayContent); + final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD, + "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD, + "openingWindowBelow"); + openingWindowBelow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + taskCloseTransition.addParticipant(closeTask, false); + taskOpenTransition.addParticipant(openTask, true); + mActivitySnapshotController.handleCloseTaskTransition(taskCloseTransition); + mActivitySnapshotController.handleOpenTaskTransition(taskOpenTransition); + + assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size()); + assertEquals(closingWindowBelow.mActivityRecord, + mActivitySnapshotController.mPendingRemoveActivity.valueAt(0)); + assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size()); + assertEquals(openingWindowBelow.mActivityRecord, + mActivitySnapshotController.mPendingLoadActivity.valueAt(0)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java new file mode 100644 index 000000000000..83af181481d9 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 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 android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; + +import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE; +import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN; +import static com.android.server.wm.SnapshotController.TASK_CLOSE; +import static com.android.server.wm.SnapshotController.TASK_OPEN; + +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +/** + * Test class for {@link SnapshotController}. + * + * Build/Install/Run: + * * atest WmTests:AppSnapshotControllerTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppSnapshotControllerTests extends WindowTestsBase { + final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>(); + final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>(); + + final TransitionMonitor mOpenActivityMonitor = new TransitionMonitor(); + final TransitionMonitor mCloseActivityMonitor = new TransitionMonitor(); + final TransitionMonitor mOpenTaskMonitor = new TransitionMonitor(); + final TransitionMonitor mCloseTaskMonitor = new TransitionMonitor(); + + @Before + public void setUp() throws Exception { + resetStatus(); + mWm.mSnapshotController.registerTransitionStateConsumer( + ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition); + mWm.mSnapshotController.registerTransitionStateConsumer( + ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition); + mWm.mSnapshotController.registerTransitionStateConsumer( + TASK_CLOSE, mCloseTaskMonitor::handleTransition); + mWm.mSnapshotController.registerTransitionStateConsumer( + TASK_OPEN, mOpenTaskMonitor::handleTransition); + } + + @After + public void tearDown() throws Exception { + mWm.mSnapshotController.unregisterTransitionStateConsumer( + ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition); + mWm.mSnapshotController.unregisterTransitionStateConsumer( + ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition); + mWm.mSnapshotController.unregisterTransitionStateConsumer( + TASK_CLOSE, mCloseTaskMonitor::handleTransition); + mWm.mSnapshotController.unregisterTransitionStateConsumer( + TASK_OPEN, mOpenTaskMonitor::handleTransition); + } + + private static class TransitionMonitor { + private final ArraySet<WindowContainer> mOpenParticipant = new ArraySet<>(); + private final ArraySet<WindowContainer> mCloseParticipant = new ArraySet<>(); + void handleTransition(SnapshotController.TransitionState<ActivityRecord> state) { + mOpenParticipant.addAll(state.getParticipant(true /* open */)); + mCloseParticipant.addAll(state.getParticipant(false /* close */)); + } + void reset() { + mOpenParticipant.clear(); + mCloseParticipant.clear(); + } + } + + private void resetStatus() { + mClosingApps.clear(); + mOpeningApps.clear(); + mOpenActivityMonitor.reset(); + mCloseActivityMonitor.reset(); + mOpenTaskMonitor.reset(); + mCloseTaskMonitor.reset(); + } + + @Test + public void testHandleAppTransition_openActivityTransition() { + final Task task = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState openingWindow = createAppWindow(task, + ACTIVITY_TYPE_STANDARD, "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + mClosingApps.add(closingWindow.mActivityRecord); + mOpeningApps.add(openingWindow.mActivityRecord); + mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps); + assertTrue(mOpenActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord)); + assertTrue(mOpenActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord)); + } + + @Test + public void testHandleAppTransition_closeActivityTransition() { + final Task task = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + final WindowState openingWindow = createAppWindow(task, + ACTIVITY_TYPE_STANDARD, "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + mClosingApps.add(closingWindow.mActivityRecord); + mOpeningApps.add(openingWindow.mActivityRecord); + mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps); + assertTrue(mCloseActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord)); + assertTrue(mCloseActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord)); + } + + @Test + public void testHandleAppTransition_TaskTransition() { + final Task closeTask = createTask(mDisplayContent); + // note for createAppWindow: the new child is added at index 0 + final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD, + "closingWindow"); + closingWindow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD, + "closingWindowBelow"); + closingWindowBelow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + + final Task openTask = createTask(mDisplayContent); + final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD, + "openingWindow"); + openingWindow.mActivityRecord.commitVisibility( + true /* visible */, true /* performLayout */); + final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD, + "openingWindowBelow"); + openingWindowBelow.mActivityRecord.commitVisibility( + false /* visible */, true /* performLayout */); + + mClosingApps.add(closingWindow.mActivityRecord); + mOpeningApps.add(openingWindow.mActivityRecord); + mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps); + assertTrue(mCloseTaskMonitor.mCloseParticipant.contains(closeTask)); + assertTrue(mOpenTaskMonitor.mOpenParticipant.contains(openTask)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 616d528c67fa..582d7d81cd4a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -44,6 +44,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.SnapshotController.TASK_CLOSE; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; @@ -1351,6 +1352,9 @@ public class TransitionTests extends WindowTestsBase { @Test public void testTransientLaunch() { + spyOn(mWm.mSnapshotController.mTaskSnapshotController); + mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE, + mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose); final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>(); final TransitionController controller = new TestTransitionController(mAtm) { @Override @@ -1361,7 +1365,9 @@ public class TransitionTests extends WindowTestsBase { super.dispatchLegacyAppTransitionFinished(ar); } }; - final TaskSnapshotController snapshotController = controller.mTaskSnapshotController; + controller.mSnapshotController = mWm.mSnapshotController; + final TaskSnapshotController taskSnapshotController = controller.mSnapshotController + .mTaskSnapshotController; final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -1391,7 +1397,7 @@ public class TransitionTests extends WindowTestsBase { // normally. mWm.mSyncEngine.abort(openTransition.getSyncId()); - verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false)); + verify(taskSnapshotController, times(1)).recordSnapshot(eq(task2), eq(false)); controller.finishTransition(openTransition); @@ -1421,7 +1427,7 @@ public class TransitionTests extends WindowTestsBase { // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be // called until finish). - verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false)); + verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1), eq(false)); enteringAnimReports.clear(); doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), @@ -1447,7 +1453,7 @@ public class TransitionTests extends WindowTestsBase { assertFalse(activity1.isVisible()); assertFalse(activity1.app.hasActivityInVisibleTask()); - verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false)); + verify(taskSnapshotController, times(1)).recordSnapshot(eq(task1), eq(false)); assertTrue(enteringAnimReports.contains(activity2)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index b80500ad6417..0d7cdc851ddf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1746,7 +1746,8 @@ class WindowTestsBase extends SystemServiceTestsBase { static class TestTransitionController extends TransitionController { TestTransitionController(ActivityTaskManagerService atms) { super(atms); - mTaskSnapshotController = mock(TaskSnapshotController.class); + doReturn(this).when(atms).getTransitionController(); + mSnapshotController = mock(SnapshotController.class); mTransitionTracer = mock(TransitionTracer.class); } } |