PageRenderTime 118ms CodeModel.GetById 42ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java

https://github.com/android/platform_frameworks_base
Java | 730 lines | 496 code | 76 blank | 158 comment | 88 complexity | 070d49e213bc00b8f211274377bd637e MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright (C) 2021 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.android.wm.shell.transition;
  17. import static android.view.WindowManager.TRANSIT_CHANGE;
  18. import static android.view.WindowManager.TRANSIT_CLOSE;
  19. import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
  20. import static android.view.WindowManager.TRANSIT_OPEN;
  21. import static android.view.WindowManager.TRANSIT_TO_BACK;
  22. import static android.view.WindowManager.TRANSIT_TO_FRONT;
  23. import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
  24. import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
  25. import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
  26. import android.annotation.NonNull;
  27. import android.annotation.Nullable;
  28. import android.content.ContentResolver;
  29. import android.content.Context;
  30. import android.database.ContentObserver;
  31. import android.os.IBinder;
  32. import android.os.RemoteException;
  33. import android.os.SystemProperties;
  34. import android.provider.Settings;
  35. import android.util.Log;
  36. import android.view.SurfaceControl;
  37. import android.view.WindowManager;
  38. import android.window.IRemoteTransition;
  39. import android.window.ITransitionPlayer;
  40. import android.window.TransitionFilter;
  41. import android.window.TransitionInfo;
  42. import android.window.TransitionRequestInfo;
  43. import android.window.WindowContainerTransaction;
  44. import android.window.WindowContainerTransactionCallback;
  45. import android.window.WindowOrganizer;
  46. import androidx.annotation.BinderThread;
  47. import com.android.internal.R;
  48. import com.android.internal.annotations.VisibleForTesting;
  49. import com.android.internal.protolog.common.ProtoLog;
  50. import com.android.wm.shell.ShellTaskOrganizer;
  51. import com.android.wm.shell.common.RemoteCallable;
  52. import com.android.wm.shell.common.ShellExecutor;
  53. import com.android.wm.shell.common.TransactionPool;
  54. import com.android.wm.shell.common.annotations.ExternalThread;
  55. import com.android.wm.shell.protolog.ShellProtoLogGroup;
  56. import java.util.ArrayList;
  57. import java.util.Arrays;
  58. /** Plays transition animations */
  59. public class Transitions implements RemoteCallable<Transitions> {
  60. static final String TAG = "ShellTransitions";
  61. /** Set to {@code true} to enable shell transitions. */
  62. public static final boolean ENABLE_SHELL_TRANSITIONS =
  63. SystemProperties.getBoolean("persist.debug.shell_transit", false);
  64. /** Transition type for dismissing split-screen via dragging the divider off the screen. */
  65. public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1;
  66. /** Transition type for launching 2 tasks simultaneously. */
  67. public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
  68. private final WindowOrganizer mOrganizer;
  69. private final Context mContext;
  70. private final ShellExecutor mMainExecutor;
  71. private final ShellExecutor mAnimExecutor;
  72. private final TransitionPlayerImpl mPlayerImpl;
  73. private final RemoteTransitionHandler mRemoteTransitionHandler;
  74. private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
  75. /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
  76. private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
  77. private float mTransitionAnimationScaleSetting = 1.0f;
  78. private static final class ActiveTransition {
  79. IBinder mToken = null;
  80. TransitionHandler mHandler = null;
  81. boolean mMerged = false;
  82. TransitionInfo mInfo = null;
  83. SurfaceControl.Transaction mStartT = null;
  84. SurfaceControl.Transaction mFinishT = null;
  85. }
  86. /** Keeps track of currently playing transitions in the order of receipt. */
  87. private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
  88. public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
  89. @NonNull Context context, @NonNull ShellExecutor mainExecutor,
  90. @NonNull ShellExecutor animExecutor) {
  91. mOrganizer = organizer;
  92. mContext = context;
  93. mMainExecutor = mainExecutor;
  94. mAnimExecutor = animExecutor;
  95. mPlayerImpl = new TransitionPlayerImpl();
  96. // The very last handler (0 in the list) should be the default one.
  97. mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
  98. // Next lowest priority is remote transitions.
  99. mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
  100. mHandlers.add(mRemoteTransitionHandler);
  101. ContentResolver resolver = context.getContentResolver();
  102. mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
  103. Settings.Global.TRANSITION_ANIMATION_SCALE,
  104. context.getResources().getFloat(
  105. R.dimen.config_appTransitionAnimationDurationScaleDefault));
  106. dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
  107. resolver.registerContentObserver(
  108. Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
  109. new SettingsObserver());
  110. }
  111. private Transitions() {
  112. mOrganizer = null;
  113. mContext = null;
  114. mMainExecutor = null;
  115. mAnimExecutor = null;
  116. mPlayerImpl = null;
  117. mRemoteTransitionHandler = null;
  118. }
  119. public ShellTransitions asRemoteTransitions() {
  120. return mImpl;
  121. }
  122. @Override
  123. public Context getContext() {
  124. return mContext;
  125. }
  126. @Override
  127. public ShellExecutor getRemoteCallExecutor() {
  128. return mMainExecutor;
  129. }
  130. private void dispatchAnimScaleSetting(float scale) {
  131. for (int i = mHandlers.size() - 1; i >= 0; --i) {
  132. mHandlers.get(i).setAnimScaleSetting(scale);
  133. }
  134. }
  135. /** Create an empty/non-registering transitions object for system-ui tests. */
  136. @VisibleForTesting
  137. public static ShellTransitions createEmptyForTesting() {
  138. return new ShellTransitions() {
  139. @Override
  140. public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
  141. @androidx.annotation.NonNull IRemoteTransition remoteTransition) {
  142. // Do nothing
  143. }
  144. @Override
  145. public void unregisterRemote(
  146. @androidx.annotation.NonNull IRemoteTransition remoteTransition) {
  147. // Do nothing
  148. }
  149. };
  150. }
  151. /** Register this transition handler with Core */
  152. public void register(ShellTaskOrganizer taskOrganizer) {
  153. if (mPlayerImpl == null) return;
  154. taskOrganizer.registerTransitionPlayer(mPlayerImpl);
  155. }
  156. /**
  157. * Adds a handler candidate.
  158. * @see TransitionHandler
  159. */
  160. public void addHandler(@NonNull TransitionHandler handler) {
  161. mHandlers.add(handler);
  162. }
  163. public ShellExecutor getMainExecutor() {
  164. return mMainExecutor;
  165. }
  166. public ShellExecutor getAnimExecutor() {
  167. return mAnimExecutor;
  168. }
  169. /** Only use this in tests. This is used to avoid running animations during tests. */
  170. @VisibleForTesting
  171. void replaceDefaultHandlerForTest(TransitionHandler handler) {
  172. mHandlers.set(0, handler);
  173. }
  174. /** Register a remote transition to be used when `filter` matches an incoming transition */
  175. public void registerRemote(@NonNull TransitionFilter filter,
  176. @NonNull IRemoteTransition remoteTransition) {
  177. mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
  178. }
  179. /** Unregisters a remote transition and all associated filters */
  180. public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
  181. mRemoteTransitionHandler.removeFiltered(remoteTransition);
  182. }
  183. /** @return true if the transition was triggered by opening something vs closing something */
  184. public static boolean isOpeningType(@WindowManager.TransitionType int type) {
  185. return type == TRANSIT_OPEN
  186. || type == TRANSIT_TO_FRONT
  187. || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
  188. }
  189. /** @return true if the transition was triggered by closing something vs opening something */
  190. public static boolean isClosingType(@WindowManager.TransitionType int type) {
  191. return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
  192. }
  193. /**
  194. * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
  195. */
  196. private static void setupStartState(@NonNull TransitionInfo info,
  197. @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
  198. boolean isOpening = isOpeningType(info.getType());
  199. for (int i = info.getChanges().size() - 1; i >= 0; --i) {
  200. final TransitionInfo.Change change = info.getChanges().get(i);
  201. final SurfaceControl leash = change.getLeash();
  202. final int mode = info.getChanges().get(i).getMode();
  203. // Don't move anything that isn't independent within its parents
  204. if (!TransitionInfo.isIndependent(change, info)) {
  205. if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
  206. t.show(leash);
  207. t.setMatrix(leash, 1, 0, 0, 1);
  208. t.setAlpha(leash, 1.f);
  209. t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
  210. }
  211. continue;
  212. }
  213. if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
  214. t.show(leash);
  215. t.setMatrix(leash, 1, 0, 0, 1);
  216. if (isOpening
  217. // If this is a transferred starting window, we want it immediately visible.
  218. && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
  219. t.setAlpha(leash, 0.f);
  220. // fix alpha in finish transaction in case the animator itself no-ops.
  221. finishT.setAlpha(leash, 1.f);
  222. }
  223. } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
  224. // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates.
  225. // As a result, we actually can't hide it's WindowToken because there may not be a
  226. // transition associated with it becoming visible again. Fortunately, since it is
  227. // always z-ordered to the back, we don't have to worry about it flickering to the
  228. // front during reparenting, so the hide here isn't necessary for it.
  229. if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) {
  230. finishT.hide(leash);
  231. }
  232. }
  233. }
  234. }
  235. /**
  236. * Reparents all participants into a shared parent and orders them based on: the global transit
  237. * type, their transit mode, and their destination z-order.
  238. */
  239. private static void setupAnimHierarchy(@NonNull TransitionInfo info,
  240. @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
  241. boolean isOpening = isOpeningType(info.getType());
  242. if (info.getRootLeash().isValid()) {
  243. t.show(info.getRootLeash());
  244. }
  245. // Put animating stuff above this line and put static stuff below it.
  246. int zSplitLine = info.getChanges().size();
  247. // changes should be ordered top-to-bottom in z
  248. for (int i = info.getChanges().size() - 1; i >= 0; --i) {
  249. final TransitionInfo.Change change = info.getChanges().get(i);
  250. final SurfaceControl leash = change.getLeash();
  251. final int mode = info.getChanges().get(i).getMode();
  252. // Don't reparent anything that isn't independent within its parents
  253. if (!TransitionInfo.isIndependent(change, info)) {
  254. continue;
  255. }
  256. boolean hasParent = change.getParent() != null;
  257. if (!hasParent) {
  258. t.reparent(leash, info.getRootLeash());
  259. t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
  260. change.getStartAbsBounds().top - info.getRootOffset().y);
  261. }
  262. // Put all the OPEN/SHOW on top
  263. if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
  264. if (isOpening) {
  265. // put on top
  266. t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
  267. } else {
  268. // put on bottom
  269. t.setLayer(leash, zSplitLine - i);
  270. }
  271. } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
  272. if (isOpening) {
  273. // put on bottom and leave visible
  274. t.setLayer(leash, zSplitLine - i);
  275. } else {
  276. // put on top
  277. t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
  278. }
  279. } else { // CHANGE or other
  280. t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
  281. }
  282. }
  283. }
  284. private int findActiveTransition(IBinder token) {
  285. for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
  286. if (mActiveTransitions.get(i).mToken == token) return i;
  287. }
  288. return -1;
  289. }
  290. @VisibleForTesting
  291. void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
  292. @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
  293. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
  294. transitionToken, info);
  295. final int activeIdx = findActiveTransition(transitionToken);
  296. if (activeIdx < 0) {
  297. throw new IllegalStateException("Got transitionReady for non-active transition "
  298. + transitionToken + ". expecting one of "
  299. + Arrays.toString(mActiveTransitions.stream().map(
  300. activeTransition -> activeTransition.mToken).toArray()));
  301. }
  302. if (!info.getRootLeash().isValid()) {
  303. // Invalid root-leash implies that the transition is empty/no-op, so just do
  304. // housekeeping and return.
  305. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
  306. transitionToken, info);
  307. t.apply();
  308. onAbort(transitionToken);
  309. return;
  310. }
  311. final ActiveTransition active = mActiveTransitions.get(activeIdx);
  312. active.mInfo = info;
  313. active.mStartT = t;
  314. active.mFinishT = finishT;
  315. setupStartState(active.mInfo, active.mStartT, active.mFinishT);
  316. if (activeIdx > 0) {
  317. // This is now playing at the same time as an existing animation, so try merging it.
  318. attemptMergeTransition(mActiveTransitions.get(0), active);
  319. return;
  320. }
  321. // The normal case, just play it.
  322. playTransition(active);
  323. }
  324. /**
  325. * Attempt to merge by delegating the transition start to the handler of the currently
  326. * playing transition.
  327. */
  328. void attemptMergeTransition(@NonNull ActiveTransition playing,
  329. @NonNull ActiveTransition merging) {
  330. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
  331. + " another transition %s is still animating. Notify the animating transition"
  332. + " in case they can be merged", merging.mToken, playing.mToken);
  333. playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
  334. playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
  335. }
  336. boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
  337. return handler.startAnimation(active.mToken, active.mInfo, active.mStartT,
  338. (wct, cb) -> onFinish(active.mToken, wct, cb));
  339. }
  340. void playTransition(@NonNull ActiveTransition active) {
  341. setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
  342. // If a handler already chose to run this animation, try delegating to it first.
  343. if (active.mHandler != null) {
  344. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
  345. active.mHandler);
  346. if (startAnimation(active, active.mHandler)) {
  347. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
  348. return;
  349. }
  350. }
  351. // Otherwise give every other handler a chance (in order)
  352. for (int i = mHandlers.size() - 1; i >= 0; --i) {
  353. if (mHandlers.get(i) == active.mHandler) continue;
  354. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
  355. mHandlers.get(i));
  356. if (startAnimation(active, mHandlers.get(i))) {
  357. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
  358. mHandlers.get(i));
  359. active.mHandler = mHandlers.get(i);
  360. return;
  361. }
  362. }
  363. throw new IllegalStateException(
  364. "This shouldn't happen, maybe the default handler is broken.");
  365. }
  366. /** Special version of finish just for dealing with no-op/invalid transitions. */
  367. private void onAbort(IBinder transition) {
  368. final int activeIdx = findActiveTransition(transition);
  369. if (activeIdx < 0) return;
  370. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
  371. "Transition animation aborted due to no-op, notifying core %s", transition);
  372. mActiveTransitions.remove(activeIdx);
  373. mOrganizer.finishTransition(transition, null /* wct */, null /* wctCB */);
  374. }
  375. private void onFinish(IBinder transition,
  376. @Nullable WindowContainerTransaction wct,
  377. @Nullable WindowContainerTransactionCallback wctCB) {
  378. int activeIdx = findActiveTransition(transition);
  379. if (activeIdx < 0) {
  380. Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
  381. + " a handler didn't properly deal with a merge.", new RuntimeException());
  382. return;
  383. } else if (activeIdx > 0) {
  384. // This transition was merged.
  385. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s",
  386. transition);
  387. final ActiveTransition active = mActiveTransitions.get(activeIdx);
  388. active.mMerged = true;
  389. if (active.mHandler != null) {
  390. active.mHandler.onTransitionMerged(active.mToken);
  391. }
  392. return;
  393. }
  394. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
  395. "Transition animation finished, notifying core %s", transition);
  396. // Merge all relevant transactions together
  397. SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
  398. for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
  399. final ActiveTransition toMerge = mActiveTransitions.get(iA);
  400. if (!toMerge.mMerged) break;
  401. // Include start. It will be a no-op if it was already applied. Otherwise, we need it
  402. // to maintain consistent state.
  403. fullFinish.merge(mActiveTransitions.get(iA).mStartT);
  404. fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
  405. }
  406. fullFinish.apply();
  407. // Now perform all the finishes.
  408. mActiveTransitions.remove(activeIdx);
  409. mOrganizer.finishTransition(transition, wct, wctCB);
  410. while (activeIdx < mActiveTransitions.size()) {
  411. if (!mActiveTransitions.get(activeIdx).mMerged) break;
  412. ActiveTransition merged = mActiveTransitions.remove(activeIdx);
  413. mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
  414. }
  415. if (mActiveTransitions.size() <= activeIdx) {
  416. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
  417. + "finished");
  418. return;
  419. }
  420. // Start animating the next active transition
  421. final ActiveTransition next = mActiveTransitions.get(activeIdx);
  422. if (next.mInfo == null) {
  423. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
  424. + " finished, but it isn't ready yet.");
  425. return;
  426. }
  427. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
  428. + " finished, so start the next one.");
  429. playTransition(next);
  430. // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
  431. // finished immediately)
  432. activeIdx = findActiveTransition(next.mToken);
  433. if (activeIdx < 0) {
  434. // This means 'next' finished immediately and thus re-entered this function. Since
  435. // that is the case, just return here since all relevant logic has already run in the
  436. // re-entered call.
  437. return;
  438. }
  439. // This logic is also convoluted because 'next' may finish immediately in response to any of
  440. // the merge requests (eg. if it decided to "cancel" itself).
  441. int mergeIdx = activeIdx + 1;
  442. while (mergeIdx < mActiveTransitions.size()) {
  443. ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
  444. if (mergeCandidate.mMerged) {
  445. throw new IllegalStateException("Can't merge a transition after not-merging"
  446. + " a preceding one.");
  447. }
  448. attemptMergeTransition(next, mergeCandidate);
  449. mergeIdx = findActiveTransition(mergeCandidate.mToken);
  450. if (mergeIdx < 0) {
  451. // This means 'next' finished immediately and thus re-entered this function. Since
  452. // that is the case, just return here since all relevant logic has already run in
  453. // the re-entered call.
  454. return;
  455. }
  456. ++mergeIdx;
  457. }
  458. }
  459. void requestStartTransition(@NonNull IBinder transitionToken,
  460. @Nullable TransitionRequestInfo request) {
  461. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
  462. transitionToken, request);
  463. if (findActiveTransition(transitionToken) >= 0) {
  464. throw new RuntimeException("Transition already started " + transitionToken);
  465. }
  466. final ActiveTransition active = new ActiveTransition();
  467. WindowContainerTransaction wct = null;
  468. for (int i = mHandlers.size() - 1; i >= 0; --i) {
  469. wct = mHandlers.get(i).handleRequest(transitionToken, request);
  470. if (wct != null) {
  471. active.mHandler = mHandlers.get(i);
  472. break;
  473. }
  474. }
  475. active.mToken = mOrganizer.startTransition(
  476. request.getType(), transitionToken, wct);
  477. mActiveTransitions.add(active);
  478. }
  479. /** Start a new transition directly. */
  480. public IBinder startTransition(@WindowManager.TransitionType int type,
  481. @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
  482. final ActiveTransition active = new ActiveTransition();
  483. active.mHandler = handler;
  484. active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
  485. mActiveTransitions.add(active);
  486. return active.mToken;
  487. }
  488. /**
  489. * Interface for a callback that must be called after a TransitionHandler finishes playing an
  490. * animation.
  491. */
  492. public interface TransitionFinishCallback {
  493. /**
  494. * This must be called on the main thread when a transition finishes playing an animation.
  495. * The transition must not touch the surfaces after this has been called.
  496. *
  497. * @param wct A WindowContainerTransaction to run along with the transition clean-up.
  498. * @param wctCB A sync callback that will be run when the transition clean-up is done and
  499. * wct has been applied.
  500. */
  501. void onTransitionFinished(@Nullable WindowContainerTransaction wct,
  502. @Nullable WindowContainerTransactionCallback wctCB);
  503. }
  504. /**
  505. * Interface for something which can handle a subset of transitions.
  506. */
  507. public interface TransitionHandler {
  508. /**
  509. * Starts a transition animation. This is always called if handleRequest returned non-null
  510. * for a particular transition. Otherwise, it is only called if no other handler before
  511. * it handled the transition.
  512. *
  513. * @param finishCallback Call this when finished. This MUST be called on main thread.
  514. * @return true if transition was handled, false if not (falls-back to default).
  515. */
  516. boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
  517. @NonNull SurfaceControl.Transaction t,
  518. @NonNull TransitionFinishCallback finishCallback);
  519. /**
  520. * Attempts to merge a different transition's animation into an animation that this handler
  521. * is currently playing. If a merge is not possible/supported, this should be a no-op.
  522. *
  523. * This gets called if another transition becomes ready while this handler is still playing
  524. * an animation. This is called regardless of whether this handler claims to support that
  525. * particular transition or not.
  526. *
  527. * When this happens, there are 2 options:
  528. * 1. Do nothing. This effectively rejects the merge request. This is the "safest" option.
  529. * 2. Merge the incoming transition into this one. The implementation is up to this
  530. * handler. To indicate that this handler has "consumed" the merge transition, it
  531. * must call the finishCallback immediately, or at-least before the original
  532. * transition's finishCallback is called.
  533. *
  534. * @param transition This is the transition that wants to be merged.
  535. * @param info Information about what is changing in the transition.
  536. * @param t Contains surface changes that resulted from the transition.
  537. * @param mergeTarget This is the transition that we are attempting to merge with (ie. the
  538. * one this handler is currently already animating).
  539. * @param finishCallback Call this if merged. This MUST be called on main thread.
  540. */
  541. default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
  542. @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
  543. @NonNull TransitionFinishCallback finishCallback) { }
  544. /**
  545. * Potentially handles a startTransition request.
  546. *
  547. * @param transition The transition whose start is being requested.
  548. * @param request Information about what is requested.
  549. * @return WCT to apply with transition-start or null. If a WCT is returned here, this
  550. * handler will be the first in line to animate.
  551. */
  552. @Nullable
  553. WindowContainerTransaction handleRequest(@NonNull IBinder transition,
  554. @NonNull TransitionRequestInfo request);
  555. /**
  556. * Called when a transition which was already "claimed" by this handler has been merged
  557. * into another animation. Gives this handler a chance to clean-up any expectations.
  558. */
  559. default void onTransitionMerged(@NonNull IBinder transition) { }
  560. /**
  561. * Sets transition animation scale settings value to handler.
  562. *
  563. * @param scale The setting value of transition animation scale.
  564. */
  565. default void setAnimScaleSetting(float scale) {}
  566. }
  567. @BinderThread
  568. private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
  569. @Override
  570. public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
  571. SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
  572. throws RemoteException {
  573. mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
  574. iBinder, transitionInfo, t, finishT));
  575. }
  576. @Override
  577. public void requestStartTransition(IBinder iBinder,
  578. TransitionRequestInfo request) throws RemoteException {
  579. mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
  580. }
  581. }
  582. /**
  583. * The interface for calls from outside the Shell, within the host process.
  584. */
  585. @ExternalThread
  586. private class ShellTransitionImpl implements ShellTransitions {
  587. private IShellTransitionsImpl mIShellTransitions;
  588. @Override
  589. public IShellTransitions createExternalInterface() {
  590. if (mIShellTransitions != null) {
  591. mIShellTransitions.invalidate();
  592. }
  593. mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
  594. return mIShellTransitions;
  595. }
  596. @Override
  597. public void registerRemote(@NonNull TransitionFilter filter,
  598. @NonNull IRemoteTransition remoteTransition) {
  599. mMainExecutor.execute(() -> {
  600. mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
  601. });
  602. }
  603. @Override
  604. public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
  605. mMainExecutor.execute(() -> {
  606. mRemoteTransitionHandler.removeFiltered(remoteTransition);
  607. });
  608. }
  609. }
  610. /**
  611. * The interface for calls from outside the host process.
  612. */
  613. @BinderThread
  614. private static class IShellTransitionsImpl extends IShellTransitions.Stub {
  615. private Transitions mTransitions;
  616. IShellTransitionsImpl(Transitions transitions) {
  617. mTransitions = transitions;
  618. }
  619. /**
  620. * Invalidates this instance, preventing future calls from updating the controller.
  621. */
  622. void invalidate() {
  623. mTransitions = null;
  624. }
  625. @Override
  626. public void registerRemote(@NonNull TransitionFilter filter,
  627. @NonNull IRemoteTransition remoteTransition) {
  628. executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
  629. (transitions) -> {
  630. transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
  631. });
  632. }
  633. @Override
  634. public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
  635. executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
  636. (transitions) -> {
  637. transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
  638. });
  639. }
  640. }
  641. private class SettingsObserver extends ContentObserver {
  642. SettingsObserver() {
  643. super(null);
  644. }
  645. @Override
  646. public void onChange(boolean selfChange) {
  647. super.onChange(selfChange);
  648. mTransitionAnimationScaleSetting = Settings.Global.getFloat(
  649. mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
  650. mTransitionAnimationScaleSetting);
  651. mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
  652. }
  653. }
  654. }