Loading core/java/android/view/WindowManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -453,6 +453,11 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_WAKE = 11; /** * The screen is turning off. This is used as a message to stop all animations. * @hide */ int TRANSIT_SLEEP = 12; /** * The first slot for custom transition types. Callers (like Shell) can make use of custom * transition types for dealing with special cases. These types are effectively ignored by Loading @@ -462,7 +467,7 @@ public interface WindowManager extends ViewManager { * implementation. * @hide */ int TRANSIT_FIRST_CUSTOM = 12; int TRANSIT_FIRST_CUSTOM = 13; /** * @hide Loading @@ -480,6 +485,7 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_PIP, TRANSIT_WAKE, TRANSIT_SLEEP, TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -1473,6 +1479,7 @@ public interface WindowManager extends ViewManager { case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE"; case TRANSIT_PIP: return "PIP"; case TRANSIT_WAKE: return "WAKE"; case TRANSIT_SLEEP: return "SLEEP"; case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM"; default: if (type > TRANSIT_FIRST_CUSTOM) { Loading data/etc/services.core.protolog.json +6 −6 Original line number Diff line number Diff line Loading @@ -1297,6 +1297,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, "-894942237": { "message": "Force Playing Transition: %d", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "-883738232": { "message": "Adding more than one toast window for UID at a time.", "level": "WARN", Loading Loading @@ -4243,12 +4249,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, "1878927091": { "message": "prepareSurface: No changes in animation for %s", "level": "VERBOSE", "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +9 −3 Original line number Diff line number Diff line Loading @@ -450,7 +450,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { // Already done, so no need to end it. return; } if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { // queue since no actual animation. } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) { boolean ended = mSplitHandler.end(); // If split couldn't end (because it is remote), then don't end everything else Loading @@ -464,8 +466,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else { mPipHandler.end(); } } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { // queue } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { mPipHandler.end(); if (mixed.mLeftoversHandler != null) { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * 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.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; import android.util.Log; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import java.util.ArrayList; /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. * * There should only be one SleepHandler and it is used explicitly by {@link Transitions} so we * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { startTransaction.apply(); finishCallback.onTransitionFinished(null, null); mSleepTransitions.remove(transition); return true; } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { mSleepTransitions.add(transition); return new WindowContainerTransaction(); } @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction) { Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense"); mSleepTransitions.remove(transition); } } libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +100 −16 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; Loading Loading @@ -124,6 +125,7 @@ public class Transitions implements RemoteCallable<Transitions> { private final DisplayController mDisplayController; private final ShellController mShellController; private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final SleepHandler mSleepHandler = new SleepHandler(); private boolean mIsRegistered = false; Loading @@ -137,6 +139,14 @@ public class Transitions implements RemoteCallable<Transitions> { private float mTransitionAnimationScaleSetting = 1.0f; /** * How much time we allow for an animation to finish itself on sleep. If it takes longer, we * will force-finish it (on this end) which may leave it in a bad state but won't hang the * device. This needs to be pretty small because it is an allowance for each queued animation, * however it can't be too small since there is some potential IPC involved. */ private static final int SLEEP_ALLOWANCE_MS = 120; private static final class ActiveTransition { IBinder mToken; TransitionHandler mHandler; Loading Loading @@ -478,11 +488,29 @@ public class Transitions implements RemoteCallable<Transitions> { + Arrays.toString(mActiveTransitions.stream().map( activeTransition -> activeTransition.mToken).toArray())); } final ActiveTransition active = mActiveTransitions.get(activeIdx); for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT); } if (info.getType() == TRANSIT_SLEEP) { if (activeIdx > 0) { active.mInfo = info; active.mStartT = t; active.mFinishT = finishT; if (!info.getRootLeash().isValid()) { // Shell has some debug settings which makes calling binders with invalid // surfaces crash, so replace it with a "real" one. info.setRootLeash(new SurfaceControl.Builder().setName("Invalid") .setContainerLayer().build(), 0, 0); } // Sleep starts a process of forcing all prior transitions to finish immediately finishForSleep(null /* forceFinish */); return; } } // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while // screen-off, so there might no visibility change involved. if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) { Loading Loading @@ -527,7 +555,6 @@ public class Transitions implements RemoteCallable<Transitions> { return; } final ActiveTransition active = mActiveTransitions.get(activeIdx); active.mInfo = info; active.mStartT = t; active.mFinishT = finishT; Loading Loading @@ -803,6 +830,12 @@ public class Transitions implements RemoteCallable<Transitions> { } final ActiveTransition active = new ActiveTransition(); WindowContainerTransaction wct = null; // If we have sleep, we use a special handler and we try to finish everything ASAP. if (request.getType() == TRANSIT_SLEEP) { mSleepHandler.handleRequest(transitionToken, request); active.mHandler = mSleepHandler; } else { for (int i = mHandlers.size() - 1; i >= 0; --i) { wct = mHandlers.get(i).handleRequest(transitionToken, request); if (wct != null) { Loading @@ -818,8 +851,9 @@ public class Transitions implements RemoteCallable<Transitions> { wct = new WindowContainerTransaction(); } mDisplayController.getChangeController().dispatchOnDisplayChange(wct, change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), null /* newDisplayAreaInfo */); change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), null /* newDisplayAreaInfo */); } } } mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); Loading @@ -845,6 +879,56 @@ public class Transitions implements RemoteCallable<Transitions> { return active.mToken; } /** * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this * as both a way to reduce unnecessary work (animations not visible while screen off) and as a * failsafe to unblock "stuck" animations (in particular remote animations). * * This works by "merging" the sleep transition into the currently-playing transition (even if * its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish * within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and * send an abort/consumed message). * * This is then repeated until there are no more pending sleep transitions. * * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge * signal to -- so it will be force-finished if it's still running. */ private void finishForSleep(@Nullable IBinder forceFinish) { if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) { return; } if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) { Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + mActiveTransitions.get(0).mToken); onFinish(mActiveTransitions.get(0).mToken, null, null, true); } final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction(); while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) { final ActiveTransition playing = mActiveTransitions.get(0); int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0)); if (sleepIdx >= 0) { // Try to signal that we are sleeping by attempting to merge the sleep transition // into the playing one. final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx); playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT, playing.mToken, (wct, cb) -> {}); } else { Log.e(TAG, "Couldn't find sleep transition in active list: " + mSleepHandler.mSleepTransitions.get(0)); } // it's possible to complete immediately. If that happens, just repeat the signal // loop until we either finish everything or start playing an animation that isn't // finishing immediately. if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) { // Give it a (very) short amount of time to process it before forcing. mMainExecutor.executeDelayed( () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS); break; } } } /** * Interface for a callback that must be called after a TransitionHandler finishes playing an * animation. Loading Loading
core/java/android/view/WindowManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -453,6 +453,11 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_WAKE = 11; /** * The screen is turning off. This is used as a message to stop all animations. * @hide */ int TRANSIT_SLEEP = 12; /** * The first slot for custom transition types. Callers (like Shell) can make use of custom * transition types for dealing with special cases. These types are effectively ignored by Loading @@ -462,7 +467,7 @@ public interface WindowManager extends ViewManager { * implementation. * @hide */ int TRANSIT_FIRST_CUSTOM = 12; int TRANSIT_FIRST_CUSTOM = 13; /** * @hide Loading @@ -480,6 +485,7 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_PIP, TRANSIT_WAKE, TRANSIT_SLEEP, TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -1473,6 +1479,7 @@ public interface WindowManager extends ViewManager { case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE"; case TRANSIT_PIP: return "PIP"; case TRANSIT_WAKE: return "WAKE"; case TRANSIT_SLEEP: return "SLEEP"; case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM"; default: if (type > TRANSIT_FIRST_CUSTOM) { Loading
data/etc/services.core.protolog.json +6 −6 Original line number Diff line number Diff line Loading @@ -1297,6 +1297,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, "-894942237": { "message": "Force Playing Transition: %d", "level": "VERBOSE", "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, "-883738232": { "message": "Adding more than one toast window for UID at a time.", "level": "WARN", Loading Loading @@ -4243,12 +4249,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, "1878927091": { "message": "prepareSurface: No changes in animation for %s", "level": "VERBOSE", "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +9 −3 Original line number Diff line number Diff line Loading @@ -450,7 +450,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { // Already done, so no need to end it. return; } if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { // queue since no actual animation. } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) { boolean ended = mSplitHandler.end(); // If split couldn't end (because it is remote), then don't end everything else Loading @@ -464,8 +466,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else { mPipHandler.end(); } } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { // queue } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { mPipHandler.end(); if (mixed.mLeftoversHandler != null) { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java 0 → 100644 +65 −0 Original line number Diff line number Diff line /* * 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.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; import android.util.Log; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import java.util.ArrayList; /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. * * There should only be one SleepHandler and it is used explicitly by {@link Transitions} so we * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { startTransaction.apply(); finishCallback.onTransitionFinished(null, null); mSleepTransitions.remove(transition); return true; } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { mSleepTransitions.add(transition); return new WindowContainerTransaction(); } @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction) { Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense"); mSleepTransitions.remove(transition); } }
libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +100 −16 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; Loading Loading @@ -124,6 +125,7 @@ public class Transitions implements RemoteCallable<Transitions> { private final DisplayController mDisplayController; private final ShellController mShellController; private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final SleepHandler mSleepHandler = new SleepHandler(); private boolean mIsRegistered = false; Loading @@ -137,6 +139,14 @@ public class Transitions implements RemoteCallable<Transitions> { private float mTransitionAnimationScaleSetting = 1.0f; /** * How much time we allow for an animation to finish itself on sleep. If it takes longer, we * will force-finish it (on this end) which may leave it in a bad state but won't hang the * device. This needs to be pretty small because it is an allowance for each queued animation, * however it can't be too small since there is some potential IPC involved. */ private static final int SLEEP_ALLOWANCE_MS = 120; private static final class ActiveTransition { IBinder mToken; TransitionHandler mHandler; Loading Loading @@ -478,11 +488,29 @@ public class Transitions implements RemoteCallable<Transitions> { + Arrays.toString(mActiveTransitions.stream().map( activeTransition -> activeTransition.mToken).toArray())); } final ActiveTransition active = mActiveTransitions.get(activeIdx); for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT); } if (info.getType() == TRANSIT_SLEEP) { if (activeIdx > 0) { active.mInfo = info; active.mStartT = t; active.mFinishT = finishT; if (!info.getRootLeash().isValid()) { // Shell has some debug settings which makes calling binders with invalid // surfaces crash, so replace it with a "real" one. info.setRootLeash(new SurfaceControl.Builder().setName("Invalid") .setContainerLayer().build(), 0, 0); } // Sleep starts a process of forcing all prior transitions to finish immediately finishForSleep(null /* forceFinish */); return; } } // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while // screen-off, so there might no visibility change involved. if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) { Loading Loading @@ -527,7 +555,6 @@ public class Transitions implements RemoteCallable<Transitions> { return; } final ActiveTransition active = mActiveTransitions.get(activeIdx); active.mInfo = info; active.mStartT = t; active.mFinishT = finishT; Loading Loading @@ -803,6 +830,12 @@ public class Transitions implements RemoteCallable<Transitions> { } final ActiveTransition active = new ActiveTransition(); WindowContainerTransaction wct = null; // If we have sleep, we use a special handler and we try to finish everything ASAP. if (request.getType() == TRANSIT_SLEEP) { mSleepHandler.handleRequest(transitionToken, request); active.mHandler = mSleepHandler; } else { for (int i = mHandlers.size() - 1; i >= 0; --i) { wct = mHandlers.get(i).handleRequest(transitionToken, request); if (wct != null) { Loading @@ -818,8 +851,9 @@ public class Transitions implements RemoteCallable<Transitions> { wct = new WindowContainerTransaction(); } mDisplayController.getChangeController().dispatchOnDisplayChange(wct, change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), null /* newDisplayAreaInfo */); change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), null /* newDisplayAreaInfo */); } } } mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); Loading @@ -845,6 +879,56 @@ public class Transitions implements RemoteCallable<Transitions> { return active.mToken; } /** * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this * as both a way to reduce unnecessary work (animations not visible while screen off) and as a * failsafe to unblock "stuck" animations (in particular remote animations). * * This works by "merging" the sleep transition into the currently-playing transition (even if * its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish * within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and * send an abort/consumed message). * * This is then repeated until there are no more pending sleep transitions. * * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge * signal to -- so it will be force-finished if it's still running. */ private void finishForSleep(@Nullable IBinder forceFinish) { if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) { return; } if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) { Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + mActiveTransitions.get(0).mToken); onFinish(mActiveTransitions.get(0).mToken, null, null, true); } final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction(); while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) { final ActiveTransition playing = mActiveTransitions.get(0); int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0)); if (sleepIdx >= 0) { // Try to signal that we are sleeping by attempting to merge the sleep transition // into the playing one. final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx); playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT, playing.mToken, (wct, cb) -> {}); } else { Log.e(TAG, "Couldn't find sleep transition in active list: " + mSleepHandler.mSleepTransitions.get(0)); } // it's possible to complete immediately. If that happens, just repeat the signal // loop until we either finish everything or start playing an animation that isn't // finishing immediately. if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) { // Give it a (very) short amount of time to process it before forcing. mMainExecutor.executeDelayed( () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS); break; } } } /** * Interface for a callback that must be called after a TransitionHandler finishes playing an * animation. Loading