blob: 60707ccdca3038a6d3e666388ba6d5c2afadba18 [file] [log] [blame]
/*
* Copyright (C) 2021 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 static android.view.WindowManager.TRANSIT_CHANGE;
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 static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.ITransitionPlayer;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.Arrays;
/** Plays transition animations */
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.debug.shell_transit", false);
/** Transition type for dismissing split-screen via dragging the divider off the screen. */
public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1;
/** Transition type for launching 2 tasks simultaneously. */
public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
private float mTransitionAnimationScaleSetting = 1.0f;
private static final class ActiveTransition {
IBinder mToken = null;
TransitionHandler mHandler = null;
boolean mMerged = false;
TransitionInfo mInfo = null;
SurfaceControl.Transaction mStartT = null;
SurfaceControl.Transaction mFinishT = null;
}
/** Keeps track of currently playing transitions in the order of receipt. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull Context context, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
ContentResolver resolver = context.getContentResolver();
mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
Settings.Global.TRANSITION_ANIMATION_SCALE,
context.getResources().getFloat(
R.dimen.config_appTransitionAnimationDurationScaleDefault));
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver());
}
private Transitions() {
mOrganizer = null;
mContext = null;
mMainExecutor = null;
mAnimExecutor = null;
mPlayerImpl = null;
mRemoteTransitionHandler = null;
}
public ShellTransitions asRemoteTransitions() {
return mImpl;
}
@Override
public Context getContext() {
return mContext;
}
@Override
public ShellExecutor getRemoteCallExecutor() {
return mMainExecutor;
}
private void dispatchAnimScaleSetting(float scale) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
mHandlers.get(i).setAnimScaleSetting(scale);
}
}
/** Create an empty/non-registering transitions object for system-ui tests. */
@VisibleForTesting
public static ShellTransitions createEmptyForTesting() {
return new ShellTransitions() {
@Override
public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
@androidx.annotation.NonNull IRemoteTransition remoteTransition) {
// Do nothing
}
@Override
public void unregisterRemote(
@androidx.annotation.NonNull IRemoteTransition remoteTransition) {
// Do nothing
}
};
}
/** Register this transition handler with Core */
public void register(ShellTaskOrganizer taskOrganizer) {
if (mPlayerImpl == null) return;
taskOrganizer.registerTransitionPlayer(mPlayerImpl);
}
/**
* Adds a handler candidate.
* @see TransitionHandler
*/
public void addHandler(@NonNull TransitionHandler handler) {
mHandlers.add(handler);
}
public ShellExecutor getMainExecutor() {
return mMainExecutor;
}
public ShellExecutor getAnimExecutor() {
return mAnimExecutor;
}
/** Only use this in tests. This is used to avoid running animations during tests. */
@VisibleForTesting
void replaceDefaultHandlerForTest(TransitionHandler handler) {
mHandlers.set(0, handler);
}
/** Register a remote transition to be used when `filter` matches an incoming transition */
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull IRemoteTransition remoteTransition) {
mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
}
/** Unregisters a remote transition and all associated filters */
public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
|| type == TRANSIT_TO_FRONT
|| type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
}
/** @return true if the transition was triggered by closing something vs opening something */
public static boolean isClosingType(@WindowManager.TransitionType int type) {
return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
}
/**
* Sets up visibility/alpha/transforms to resemble the starting state of an animation.
*/
private static void setupStartState(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
// Don't move anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
t.show(leash);
t.setMatrix(leash, 1, 0, 0, 1);
t.setAlpha(leash, 1.f);
t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
}
continue;
}
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
t.show(leash);
t.setMatrix(leash, 1, 0, 0, 1);
if (isOpening
// If this is a transferred starting window, we want it immediately visible.
&& (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
t.setAlpha(leash, 0.f);
// fix alpha in finish transaction in case the animator itself no-ops.
finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
// Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates.
// As a result, we actually can't hide it's WindowToken because there may not be a
// transition associated with it becoming visible again. Fortunately, since it is
// always z-ordered to the back, we don't have to worry about it flickering to the
// front during reparenting, so the hide here isn't necessary for it.
if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) {
finishT.hide(leash);
}
}
}
}
/**
* Reparents all participants into a shared parent and orders them based on: the global transit
* type, their transit mode, and their destination z-order.
*/
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
if (info.getRootLeash().isValid()) {
t.show(info.getRootLeash());
}
// Put animating stuff above this line and put static stuff below it.
int zSplitLine = info.getChanges().size();
// changes should be ordered top-to-bottom in z
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
// Don't reparent anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
continue;
}
boolean hasParent = change.getParent() != null;
if (!hasParent) {
t.reparent(leash, info.getRootLeash());
t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
change.getStartAbsBounds().top - info.getRootOffset().y);
}
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
// put on top
t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
} else {
// put on bottom
t.setLayer(leash, zSplitLine - i);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
if (isOpening) {
// put on bottom and leave visible
t.setLayer(leash, zSplitLine - i);
} else {
// put on top
t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
}
} else { // CHANGE or other
t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
}
}
}
private int findActiveTransition(IBinder token) {
for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
if (mActiveTransitions.get(i).mToken == token) return i;
}
return -1;
}
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
final int activeIdx = findActiveTransition(transitionToken);
if (activeIdx < 0) {
throw new IllegalStateException("Got transitionReady for non-active transition "
+ transitionToken + ". expecting one of "
+ Arrays.toString(mActiveTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
if (!info.getRootLeash().isValid()) {
// Invalid root-leash implies that the transition is empty/no-op, so just do
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
transitionToken, info);
t.apply();
onAbort(transitionToken);
return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
if (activeIdx > 0) {
// This is now playing at the same time as an existing animation, so try merging it.
attemptMergeTransition(mActiveTransitions.get(0), active);
return;
}
// The normal case, just play it.
playTransition(active);
}
/**
* Attempt to merge by delegating the transition start to the handler of the currently
* playing transition.
*/
void attemptMergeTransition(@NonNull ActiveTransition playing,
@NonNull ActiveTransition merging) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " another transition %s is still animating. Notify the animating transition"
+ " in case they can be merged", merging.mToken, playing.mToken);
playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
}
boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
return handler.startAnimation(active.mToken, active.mInfo, active.mStartT,
(wct, cb) -> onFinish(active.mToken, wct, cb));
}
void playTransition(@NonNull ActiveTransition active) {
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// If a handler already chose to run this animation, try delegating to it first.
if (active.mHandler != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
if (startAnimation(active, active.mHandler)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
}
}
// Otherwise give every other handler a chance (in order)
for (int i = mHandlers.size() - 1; i >= 0; --i) {
if (mHandlers.get(i) == active.mHandler) continue;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
mHandlers.get(i));
if (startAnimation(active, mHandlers.get(i))) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
active.mHandler = mHandlers.get(i);
return;
}
}
throw new IllegalStateException(
"This shouldn't happen, maybe the default handler is broken.");
}
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
final int activeIdx = findActiveTransition(transition);
if (activeIdx < 0) return;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation aborted due to no-op, notifying core %s", transition);
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, null /* wct */, null /* wctCB */);
}
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB) {
int activeIdx = findActiveTransition(transition);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
+ " a handler didn't properly deal with a merge.", new RuntimeException());
return;
} else if (activeIdx > 0) {
// This transition was merged.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s",
transition);
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mMerged = true;
if (active.mHandler != null) {
active.mHandler.onTransitionMerged(active.mToken);
}
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished, notifying core %s", transition);
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
final ActiveTransition toMerge = mActiveTransitions.get(iA);
if (!toMerge.mMerged) break;
// Include start. It will be a no-op if it was already applied. Otherwise, we need it
// to maintain consistent state.
fullFinish.merge(mActiveTransitions.get(iA).mStartT);
fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
}
fullFinish.apply();
// Now perform all the finishes.
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, wct, wctCB);
while (activeIdx < mActiveTransitions.size()) {
if (!mActiveTransitions.get(activeIdx).mMerged) break;
ActiveTransition merged = mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
+ "finished");
return;
}
// Start animating the next active transition
final ActiveTransition next = mActiveTransitions.get(activeIdx);
if (next.mInfo == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
+ " finished, but it isn't ready yet.");
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
+ " finished, so start the next one.");
playTransition(next);
// Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
// finished immediately)
activeIdx = findActiveTransition(next.mToken);
if (activeIdx < 0) {
// This means 'next' finished immediately and thus re-entered this function. Since
// that is the case, just return here since all relevant logic has already run in the
// re-entered call.
return;
}
// This logic is also convoluted because 'next' may finish immediately in response to any of
// the merge requests (eg. if it decided to "cancel" itself).
int mergeIdx = activeIdx + 1;
while (mergeIdx < mActiveTransitions.size()) {
ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
if (mergeCandidate.mMerged) {
throw new IllegalStateException("Can't merge a transition after not-merging"
+ " a preceding one.");
}
attemptMergeTransition(next, mergeCandidate);
mergeIdx = findActiveTransition(mergeCandidate.mToken);
if (mergeIdx < 0) {
// This means 'next' finished immediately and thus re-entered this function. Since
// that is the case, just return here since all relevant logic has already run in
// the re-entered call.
return;
}
++mergeIdx;
}
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
if (findActiveTransition(transitionToken) >= 0) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
WindowContainerTransaction wct = null;
for (int i = mHandlers.size() - 1; i >= 0; --i) {
wct = mHandlers.get(i).handleRequest(transitionToken, request);
if (wct != null) {
active.mHandler = mHandlers.get(i);
break;
}
}
active.mToken = mOrganizer.startTransition(
request.getType(), transitionToken, wct);
mActiveTransitions.add(active);
}
/** Start a new transition directly. */
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
mActiveTransitions.add(active);
return active.mToken;
}
/**
* Interface for a callback that must be called after a TransitionHandler finishes playing an
* animation.
*/
public interface TransitionFinishCallback {
/**
* This must be called on the main thread when a transition finishes playing an animation.
* The transition must not touch the surfaces after this has been called.
*
* @param wct A WindowContainerTransaction to run along with the transition clean-up.
* @param wctCB A sync callback that will be run when the transition clean-up is done and
* wct has been applied.
*/
void onTransitionFinished(@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB);
}
/**
* Interface for something which can handle a subset of transitions.
*/
public interface TransitionHandler {
/**
* Starts a transition animation. This is always called if handleRequest returned non-null
* for a particular transition. Otherwise, it is only called if no other handler before
* it handled the transition.
*
* @param finishCallback Call this when finished. This MUST be called on main thread.
* @return true if transition was handled, false if not (falls-back to default).
*/
boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t,
@NonNull TransitionFinishCallback finishCallback);
/**
* Attempts to merge a different transition's animation into an animation that this handler
* is currently playing. If a merge is not possible/supported, this should be a no-op.
*
* This gets called if another transition becomes ready while this handler is still playing
* an animation. This is called regardless of whether this handler claims to support that
* particular transition or not.
*
* When this happens, there are 2 options:
* 1. Do nothing. This effectively rejects the merge request. This is the "safest" option.
* 2. Merge the incoming transition into this one. The implementation is up to this
* handler. To indicate that this handler has "consumed" the merge transition, it
* must call the finishCallback immediately, or at-least before the original
* transition's finishCallback is called.
*
* @param transition This is the transition that wants to be merged.
* @param info Information about what is changing in the transition.
* @param t Contains surface changes that resulted from the transition.
* @param mergeTarget This is the transition that we are attempting to merge with (ie. the
* one this handler is currently already animating).
* @param finishCallback Call this if merged. This MUST be called on main thread.
*/
default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) { }
/**
* Potentially handles a startTransition request.
*
* @param transition The transition whose start is being requested.
* @param request Information about what is requested.
* @return WCT to apply with transition-start or null. If a WCT is returned here, this
* handler will be the first in line to animate.
*/
@Nullable
WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request);
/**
* Called when a transition which was already "claimed" by this handler has been merged
* into another animation. Gives this handler a chance to clean-up any expectations.
*/
default void onTransitionMerged(@NonNull IBinder transition) { }
/**
* Sets transition animation scale settings value to handler.
*
* @param scale The setting value of transition animation scale.
*/
default void setAnimScaleSetting(float scale) {}
}
@BinderThread
private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
@Override
public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
throws RemoteException {
mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
iBinder, transitionInfo, t, finishT));
}
@Override
public void requestStartTransition(IBinder iBinder,
TransitionRequestInfo request) throws RemoteException {
mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
}
}
/**
* The interface for calls from outside the Shell, within the host process.
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
private IShellTransitionsImpl mIShellTransitions;
@Override
public IShellTransitions createExternalInterface() {
if (mIShellTransitions != null) {
mIShellTransitions.invalidate();
}
mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
return mIShellTransitions;
}
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
});
}
@Override
public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class IShellTransitionsImpl extends IShellTransitions.Stub {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
mTransitions = transitions;
}
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
void invalidate() {
mTransitions = null;
}
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull IRemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
(transitions) -> {
transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
});
}
@Override
public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
(transitions) -> {
transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
}
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
super(null);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
mTransitionAnimationScaleSetting = Settings.Global.getFloat(
mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
mTransitionAnimationScaleSetting);
mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
}
}
}