• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 
17 package com.android.systemui.animation.server;
18 
19 import static android.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_OPEN;
22 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
23 import static android.view.WindowManager.TRANSIT_TO_BACK;
24 import static android.view.WindowManager.TRANSIT_TO_FRONT;
25 
26 import android.Manifest;
27 import android.annotation.Nullable;
28 import android.app.TaskInfo;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.os.Build;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 import android.view.SurfaceControl;
37 import android.window.IRemoteTransition;
38 import android.window.IRemoteTransitionFinishedCallback;
39 import android.window.RemoteTransition;
40 import android.window.TransitionFilter;
41 import android.window.TransitionInfo;
42 import android.window.TransitionInfo.Change;
43 import android.window.WindowAnimationState;
44 
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.internal.util.IndentingPrintWriter;
47 import com.android.systemui.animation.shared.IOriginTransitions;
48 import com.android.wm.shell.shared.ShellTransitions;
49 import com.android.wm.shell.shared.TransitionUtil;
50 
51 import java.util.Map;
52 import java.util.concurrent.Executor;
53 import java.util.function.Predicate;
54 
55 /** An implementation of the {@link IOriginTransitions}. */
56 public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
57     private static final String TAG = "OriginTransitions";
58     private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
59 
60     private final Object mLock = new Object();
61     private final ShellTransitions mShellTransitions;
62     private final Context mContext;
63 
64     @GuardedBy("mLock")
65     private final Map<IBinder, OriginTransitionRecord> mRecords = new ArrayMap<>();
66 
IOriginTransitionsImpl(Context context, ShellTransitions shellTransitions)67     public IOriginTransitionsImpl(Context context, ShellTransitions shellTransitions) {
68         mShellTransitions = shellTransitions;
69         mContext = context;
70     }
71 
72     @Override
makeOriginTransition( RemoteTransition launchTransition, RemoteTransition returnTransition)73     public RemoteTransition makeOriginTransition(
74             RemoteTransition launchTransition, RemoteTransition returnTransition)
75             throws RemoteException {
76         if (DEBUG) {
77             Log.d(
78                     TAG,
79                     "makeOriginTransition: (" + launchTransition + ", " + returnTransition + ")");
80         }
81         enforceRemoteTransitionPermission();
82         synchronized (mLock) {
83             OriginTransitionRecord record =
84                     new OriginTransitionRecord(launchTransition, returnTransition);
85             mRecords.put(record.getToken(), record);
86             return record.asLaunchableTransition();
87         }
88     }
89 
90     @Override
cancelOriginTransition(RemoteTransition originTransition)91     public void cancelOriginTransition(RemoteTransition originTransition) {
92         if (DEBUG) {
93             Log.d(TAG, "cancelOriginTransition: " + originTransition);
94         }
95         enforceRemoteTransitionPermission();
96         synchronized (mLock) {
97             if (!mRecords.containsKey(originTransition.asBinder())) {
98                 return;
99             }
100             mRecords.get(originTransition.asBinder()).destroy();
101         }
102     }
103 
enforceRemoteTransitionPermission()104     private void enforceRemoteTransitionPermission() {
105         mContext.enforceCallingPermission(
106                 Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
107                 "Missing permission "
108                         + Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS);
109     }
110 
dump(IndentingPrintWriter ipw)111     public void dump(IndentingPrintWriter ipw) {
112         ipw.println("IOriginTransitionsImpl");
113         ipw.println("Active records:");
114         ipw.increaseIndent();
115         synchronized (mLock) {
116             if (mRecords.isEmpty()) {
117                 ipw.println("none");
118             } else {
119                 for (OriginTransitionRecord record : mRecords.values()) {
120                     record.dump(ipw);
121                 }
122             }
123         }
124         ipw.decreaseIndent();
125     }
126 
127     /**
128      * An {@link IRemoteTransition} that delegates animation to another {@link IRemoteTransition}
129      * and notify callbacks when the transition starts.
130      */
131     private static class RemoteTransitionDelegate extends IRemoteTransition.Stub {
132         private final IRemoteTransition mTransition;
133         private final Predicate<TransitionInfo> mOnStarting;
134         private final Executor mExecutor;
135 
RemoteTransitionDelegate( Executor executor, IRemoteTransition transition, Predicate<TransitionInfo> onStarting)136         RemoteTransitionDelegate(
137                 Executor executor,
138                 IRemoteTransition transition,
139                 Predicate<TransitionInfo> onStarting) {
140             mExecutor = executor;
141             mTransition = transition;
142             mOnStarting = onStarting;
143         }
144 
145         @Override
startAnimation( IBinder token, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)146         public void startAnimation(
147                 IBinder token,
148                 TransitionInfo info,
149                 SurfaceControl.Transaction t,
150                 IRemoteTransitionFinishedCallback finishCallback)
151                 throws RemoteException {
152             if (DEBUG) {
153                 Log.d(TAG, "startAnimation: " + info);
154             }
155             if (maybeInterceptTransition(info, t, finishCallback)) {
156                 return;
157             }
158             mTransition.startAnimation(token, info, t, finishCallback);
159         }
160 
161         @Override
mergeAnimation( IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback)162         public void mergeAnimation(
163                 IBinder transition,
164                 TransitionInfo info,
165                 SurfaceControl.Transaction t,
166                 IBinder mergeTarget,
167                 IRemoteTransitionFinishedCallback finishCallback)
168                 throws RemoteException {
169             if (DEBUG) {
170                 Log.d(TAG, "mergeAnimation: " + info);
171             }
172             mTransition.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
173         }
174 
175         @Override
takeOverAnimation( IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback, WindowAnimationState[] states)176         public void takeOverAnimation(
177                 IBinder transition,
178                 TransitionInfo info,
179                 SurfaceControl.Transaction t,
180                 IRemoteTransitionFinishedCallback finishCallback,
181                 WindowAnimationState[] states)
182                 throws RemoteException {
183             if (DEBUG) {
184                 Log.d(TAG, "takeOverAnimation: " + info);
185             }
186             if (maybeInterceptTransition(info, t, finishCallback)) {
187                 return;
188             }
189             mTransition.takeOverAnimation(transition, info, t, finishCallback, states);
190         }
191 
192         @Override
onTransitionConsumed(IBinder transition, boolean aborted)193         public void onTransitionConsumed(IBinder transition, boolean aborted)
194                 throws RemoteException {
195             if (DEBUG) {
196                 Log.d(TAG, "onTransitionConsumed: aborted=" + aborted);
197             }
198             mTransition.onTransitionConsumed(transition, aborted);
199         }
200 
201         @Override
toString()202         public String toString() {
203             return "RemoteTransitionDelegate{transition=" + mTransition + "}";
204         }
205 
maybeInterceptTransition( TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)206         private boolean maybeInterceptTransition(
207                 TransitionInfo info,
208                 SurfaceControl.Transaction t,
209                 IRemoteTransitionFinishedCallback finishCallback) {
210             if (!mOnStarting.test(info)) {
211                 Log.w(TAG, "Intercepting cancelled transition " + mTransition);
212                 t.addTransactionCommittedListener(
213                                 mExecutor,
214                                 () -> {
215                                     try {
216                                         finishCallback.onTransitionFinished(null, null);
217                                     } catch (RemoteException e) {
218                                         Log.e(TAG, "Unable to report finish.", e);
219                                     }
220                                 })
221                         .apply();
222                 return true;
223             }
224             return false;
225         }
226     }
227 
228     /** A data record containing the origin transition pieces. */
229     private class OriginTransitionRecord implements IBinder.DeathRecipient {
230         private final RemoteTransition mWrappedLaunchTransition;
231         private final RemoteTransition mWrappedReturnTransition;
232 
233         @GuardedBy("mLock")
234         private boolean mDestroyed;
235 
OriginTransitionRecord(RemoteTransition launchTransition, RemoteTransition returnTransition)236         OriginTransitionRecord(RemoteTransition launchTransition, RemoteTransition returnTransition)
237                 throws RemoteException {
238             mWrappedLaunchTransition = wrap(launchTransition, this::onLaunchTransitionStarting);
239             mWrappedReturnTransition = wrap(returnTransition, this::onReturnTransitionStarting);
240             linkToDeath();
241         }
242 
onLaunchTransitionStarting(TransitionInfo info)243         private boolean onLaunchTransitionStarting(TransitionInfo info) {
244             synchronized (mLock) {
245                 if (mDestroyed) {
246                     return false;
247                 }
248                 TransitionFilter filter =
249                         createFilterForReverseTransition(
250                                 info, /* forPredictiveBackTakeover= */ false);
251                 if (filter != null) {
252                     if (DEBUG) {
253                         Log.d(TAG, "Registering filter " + filter);
254                     }
255                     mShellTransitions.registerRemote(filter, mWrappedReturnTransition);
256                 }
257                 TransitionFilter takeoverFilter =
258                         createFilterForReverseTransition(
259                                 info, /* forPredictiveBackTakeover= */ true);
260                 if (takeoverFilter != null) {
261                     if (DEBUG) {
262                         Log.d(TAG, "Registering filter for takeover " + takeoverFilter);
263                     }
264                     mShellTransitions.registerRemoteForTakeover(
265                             takeoverFilter, mWrappedReturnTransition);
266                 }
267                 return true;
268             }
269         }
270 
onReturnTransitionStarting(TransitionInfo info)271         private boolean onReturnTransitionStarting(TransitionInfo info) {
272             synchronized (mLock) {
273                 if (mDestroyed) {
274                     return false;
275                 }
276                 // Clean up stuff.
277                 destroy();
278                 return true;
279             }
280         }
281 
destroy()282         public void destroy() {
283             synchronized (mLock) {
284                 if (mDestroyed) {
285                     // Already destroyed.
286                     return;
287                 }
288                 if (DEBUG) {
289                     Log.d(TAG, "Destroying origin transition record " + this);
290                 }
291                 mDestroyed = true;
292                 unlinkToDeath();
293                 mShellTransitions.unregisterRemote(mWrappedReturnTransition);
294                 mRecords.remove(getToken());
295             }
296         }
297 
linkToDeath()298         private void linkToDeath() throws RemoteException {
299             asDelegate(mWrappedLaunchTransition).mTransition.asBinder().linkToDeath(this, 0);
300             asDelegate(mWrappedReturnTransition).mTransition.asBinder().linkToDeath(this, 0);
301         }
302 
unlinkToDeath()303         private void unlinkToDeath() {
304             asDelegate(mWrappedLaunchTransition).mTransition.asBinder().unlinkToDeath(this, 0);
305             asDelegate(mWrappedReturnTransition).mTransition.asBinder().unlinkToDeath(this, 0);
306         }
307 
getToken()308         public IBinder getToken() {
309             return asLaunchableTransition().asBinder();
310         }
311 
asLaunchableTransition()312         public RemoteTransition asLaunchableTransition() {
313             return mWrappedLaunchTransition;
314         }
315 
316         @Override
binderDied()317         public void binderDied() {
318             destroy();
319         }
320 
321         @Override
toString()322         public String toString() {
323             return "OriginTransitionRecord{launch="
324                     + mWrappedReturnTransition
325                     + ", return="
326                     + mWrappedReturnTransition
327                     + "}";
328         }
329 
dump(IndentingPrintWriter ipw)330         public void dump(IndentingPrintWriter ipw) {
331             synchronized (mLock) {
332                 ipw.println("OriginTransitionRecord");
333                 ipw.increaseIndent();
334                 ipw.println("mDestroyed: " + mDestroyed);
335                 ipw.println("Launch transition:");
336                 ipw.increaseIndent();
337                 ipw.println(mWrappedLaunchTransition);
338                 ipw.decreaseIndent();
339                 ipw.println("Return transition:");
340                 ipw.increaseIndent();
341                 ipw.println(mWrappedReturnTransition);
342                 ipw.decreaseIndent();
343                 ipw.decreaseIndent();
344             }
345         }
346 
asDelegate(RemoteTransition transition)347         private static RemoteTransitionDelegate asDelegate(RemoteTransition transition) {
348             return (RemoteTransitionDelegate) transition.getRemoteTransition();
349         }
350 
wrap( RemoteTransition transition, Predicate<TransitionInfo> onStarting)351         private RemoteTransition wrap(
352                 RemoteTransition transition, Predicate<TransitionInfo> onStarting) {
353             return new RemoteTransition(
354                     new RemoteTransitionDelegate(
355                             mContext.getMainExecutor(),
356                             transition.getRemoteTransition(),
357                             onStarting),
358                     transition.getDebugName());
359         }
360 
361         @Nullable
createFilterForReverseTransition( TransitionInfo info, boolean forPredictiveBackTakeover)362         private static TransitionFilter createFilterForReverseTransition(
363                 TransitionInfo info, boolean forPredictiveBackTakeover) {
364             TaskInfo launchingTaskInfo = null;
365             TaskInfo launchedTaskInfo = null;
366             ComponentName launchingActivity = null;
367             ComponentName launchedActivity = null;
368             for (Change change : info.getChanges()) {
369                 int mode = change.getMode();
370                 TaskInfo taskInfo = change.getTaskInfo();
371                 ComponentName activity = change.getActivityComponent();
372                 if (TransitionUtil.isClosingMode(mode)
373                         && launchingTaskInfo == null
374                         && taskInfo != null) {
375                     // Found the launching task!
376                     launchingTaskInfo = taskInfo;
377                 } else if (TransitionUtil.isOpeningMode(mode)
378                         && launchedTaskInfo == null
379                         && taskInfo != null) {
380                     // Found the launched task!
381                     launchedTaskInfo = taskInfo;
382                 } else if (TransitionUtil.isClosingMode(mode)
383                         && launchingActivity == null
384                         && activity != null) {
385                     // Found the launching activity
386                     launchingActivity = activity;
387                 } else if (TransitionUtil.isOpeningMode(mode)
388                         && launchedActivity == null
389                         && activity != null) {
390                     // Found the launched activity!
391                     launchedActivity = activity;
392                 }
393             }
394             if (DEBUG) {
395                 Log.d(
396                         TAG,
397                         "createFilterForReverseTransition: forPredictiveBackTakeover="
398                                 + forPredictiveBackTakeover
399                                 + ", launchingTaskInfo="
400                                 + launchingTaskInfo
401                                 + ", launchedTaskInfo="
402                                 + launchedTaskInfo
403                                 + ", launchingActivity="
404                                 + launchedActivity
405                                 + ", launchedActivity="
406                                 + launchedActivity);
407             }
408             if (launchingTaskInfo == null && launchingActivity == null) {
409                 Log.w(
410                         TAG,
411                         "createFilterForReverseTransition: unable to find launching task or"
412                                 + " launching activity!");
413                 return null;
414             }
415             if (launchedTaskInfo == null && launchedActivity == null) {
416                 Log.w(
417                         TAG,
418                         "createFilterForReverseTransition: unable to find launched task or launched"
419                                 + " activity!");
420                 return null;
421             }
422             if (launchedTaskInfo != null && launchedTaskInfo.launchCookies.isEmpty()) {
423                 Log.w(
424                         TAG,
425                         "createFilterForReverseTransition: skipped - launched task has no launch"
426                                 + " cookie!");
427                 return null;
428             }
429             if (forPredictiveBackTakeover && launchedTaskInfo == null) {
430                 // Predictive back take over currently only support cross-task transition.
431                 Log.d(
432                         TAG,
433                         "createFilterForReverseTransition: skipped - unable to find launched task"
434                                 + " for predictive back takeover");
435                 return null;
436             }
437             TransitionFilter filter = new TransitionFilter();
438             if (forPredictiveBackTakeover) {
439                 filter.mTypeSet = new int[] {TRANSIT_PREPARE_BACK_NAVIGATION};
440             } else {
441                 filter.mTypeSet =
442                         new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK, TRANSIT_OPEN, TRANSIT_TO_FRONT};
443             }
444 
445             // The opening activity of the return transition must match the activity we just closed.
446             TransitionFilter.Requirement req1 = new TransitionFilter.Requirement();
447             req1.mModes = new int[] {TRANSIT_OPEN, TRANSIT_TO_FRONT};
448             req1.mTopActivity =
449                     launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity;
450 
451             TransitionFilter.Requirement req2 = new TransitionFilter.Requirement();
452             if (forPredictiveBackTakeover) {
453                 req2.mModes = new int[] {TRANSIT_CHANGE};
454             } else {
455                 req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
456             }
457             if (launchedTaskInfo != null) {
458                 // For task transitions, the closing task's cookie must match the task we just
459                 // launched.
460                 req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0);
461             } else {
462                 // For activity transitions, the closing activity of the return transition must
463                 // match the activity we just launched.
464                 req2.mTopActivity = launchedActivity;
465             }
466 
467             filter.mRequirements = new TransitionFilter.Requirement[] {req1, req2};
468             return filter;
469         }
470     }
471 }
472