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 
17 package com.android.wm.shell.transition;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.ActivityTaskManager;
22 import android.os.IBinder;
23 import android.os.RemoteException;
24 import android.util.ArrayMap;
25 import android.util.Log;
26 import android.util.Pair;
27 import android.util.Slog;
28 import android.view.SurfaceControl;
29 import android.window.IRemoteTransition;
30 import android.window.IRemoteTransitionFinishedCallback;
31 import android.window.RemoteTransition;
32 import android.window.TransitionFilter;
33 import android.window.TransitionInfo;
34 import android.window.TransitionRequestInfo;
35 import android.window.WindowContainerTransaction;
36 
37 import androidx.annotation.BinderThread;
38 
39 import com.android.internal.protolog.common.ProtoLog;
40 import com.android.wm.shell.common.ShellExecutor;
41 import com.android.wm.shell.protolog.ShellProtoLogGroup;
42 
43 import java.util.ArrayList;
44 
45 /**
46  * Handler that deals with RemoteTransitions. It will only request to handle a transition
47  * if the request includes a specific remote.
48  */
49 public class RemoteTransitionHandler implements Transitions.TransitionHandler {
50     private static final String TAG = "RemoteTransitionHandler";
51 
52     private final ShellExecutor mMainExecutor;
53 
54     /** Includes remotes explicitly requested by, eg, ActivityOptions */
55     private final ArrayMap<IBinder, RemoteTransition> mRequestedRemotes = new ArrayMap<>();
56 
57     /** Ordered by specificity. Last filters will be checked first */
58     private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mFilters =
59             new ArrayList<>();
60 
61     private final ArrayMap<IBinder, RemoteDeathHandler> mDeathHandlers = new ArrayMap<>();
62 
RemoteTransitionHandler(@onNull ShellExecutor mainExecutor)63     RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
64         mMainExecutor = mainExecutor;
65     }
66 
addFiltered(TransitionFilter filter, RemoteTransition remote)67     void addFiltered(TransitionFilter filter, RemoteTransition remote) {
68         handleDeath(remote.asBinder(), null /* finishCallback */);
69         mFilters.add(new Pair<>(filter, remote));
70     }
71 
removeFiltered(RemoteTransition remote)72     void removeFiltered(RemoteTransition remote) {
73         boolean removed = false;
74         for (int i = mFilters.size() - 1; i >= 0; --i) {
75             if (mFilters.get(i).second.asBinder().equals(remote.asBinder())) {
76                 mFilters.remove(i);
77                 removed = true;
78             }
79         }
80         if (removed) {
81             unhandleDeath(remote.asBinder(), null /* finishCallback */);
82         }
83     }
84 
85     @Override
onTransitionMerged(@onNull IBinder transition)86     public void onTransitionMerged(@NonNull IBinder transition) {
87         mRequestedRemotes.remove(transition);
88     }
89 
90     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)91     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
92             @NonNull SurfaceControl.Transaction startTransaction,
93             @NonNull SurfaceControl.Transaction finishTransaction,
94             @NonNull Transitions.TransitionFinishCallback finishCallback) {
95         RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
96         if (pendingRemote == null) {
97             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
98                     + "explicit remote, search filters for match for %s", transition, info);
99             // If no explicit remote, search filters until one matches
100             for (int i = mFilters.size() - 1; i >= 0; --i) {
101                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
102                         mFilters.get(i));
103                 if (mFilters.get(i).first.matches(info)) {
104                     Slog.d(TAG, "Found filter" + mFilters.get(i));
105                     pendingRemote = mFilters.get(i).second;
106                     // Add to requested list so that it can be found for merge requests.
107                     mRequestedRemotes.put(transition, pendingRemote);
108                     break;
109                 }
110             }
111         }
112         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
113                 transition, pendingRemote);
114 
115         if (pendingRemote == null) return false;
116 
117         final RemoteTransition remote = pendingRemote;
118         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
119             @Override
120             public void onTransitionFinished(WindowContainerTransaction wct,
121                     SurfaceControl.Transaction sct) {
122                 unhandleDeath(remote.asBinder(), finishCallback);
123                 mMainExecutor.execute(() -> {
124                     if (sct != null) {
125                         finishTransaction.merge(sct);
126                     }
127                     mRequestedRemotes.remove(transition);
128                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
129                 });
130             }
131         };
132         try {
133             handleDeath(remote.asBinder(), finishCallback);
134             try {
135                 ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
136                         remote.getAppThread());
137             } catch (SecurityException e) {
138                 Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
139                         + " during unit tests");
140             }
141             remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
142         } catch (RemoteException e) {
143             Log.e(Transitions.TAG, "Error running remote transition.", e);
144             unhandleDeath(remote.asBinder(), finishCallback);
145             mRequestedRemotes.remove(transition);
146             mMainExecutor.execute(
147                     () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
148         }
149         return true;
150     }
151 
152     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)153     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
154             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
155             @NonNull Transitions.TransitionFinishCallback finishCallback) {
156         final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
157         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
158                 transition, remote);
159         if (remote == null) return;
160 
161         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
162             @Override
163             public void onTransitionFinished(WindowContainerTransaction wct,
164                     SurfaceControl.Transaction sct) {
165                 mMainExecutor.execute(() -> {
166                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
167                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
168                                 + "transition it was supposed to merge into). This usually means "
169                                 + "that the mergeTarget's RemoteTransition impl erroneously "
170                                 + "accepted/ran the merge request after finishing the mergeTarget");
171                     }
172                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
173                 });
174             }
175         };
176         try {
177             remote.mergeAnimation(transition, info, t, mergeTarget, cb);
178         } catch (RemoteException e) {
179             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
180         }
181     }
182 
183     @Override
184     @Nullable
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)185     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
186             @Nullable TransitionRequestInfo request) {
187         RemoteTransition remote = request.getRemoteTransition();
188         if (remote == null) return null;
189         mRequestedRemotes.put(transition, remote);
190         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
191                 + " for %s: %s", transition, remote);
192         return new WindowContainerTransaction();
193     }
194 
handleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)195     private void handleDeath(@NonNull IBinder remote,
196             @Nullable Transitions.TransitionFinishCallback finishCallback) {
197         synchronized (mDeathHandlers) {
198             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
199             if (deathHandler == null) {
200                 deathHandler = new RemoteDeathHandler(remote);
201                 try {
202                     remote.linkToDeath(deathHandler, 0 /* flags */);
203                 } catch (RemoteException e) {
204                     Slog.e(TAG, "Failed to link to death");
205                     return;
206                 }
207                 mDeathHandlers.put(remote, deathHandler);
208             }
209             deathHandler.addUser(finishCallback);
210         }
211     }
212 
unhandleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)213     private void unhandleDeath(@NonNull IBinder remote,
214             @Nullable Transitions.TransitionFinishCallback finishCallback) {
215         synchronized (mDeathHandlers) {
216             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
217             if (deathHandler == null) return;
218             deathHandler.removeUser(finishCallback);
219             if (deathHandler.getUserCount() == 0) {
220                 if (!deathHandler.mPendingFinishCallbacks.isEmpty()) {
221                     throw new IllegalStateException("Unhandling death for binder that still has"
222                             + " pending finishCallback(s).");
223                 }
224                 remote.unlinkToDeath(deathHandler, 0 /* flags */);
225                 mDeathHandlers.remove(remote);
226             }
227         }
228     }
229 
230     /** NOTE: binder deaths can alter the filter order */
231     private class RemoteDeathHandler implements IBinder.DeathRecipient {
232         private final IBinder mRemote;
233         private final ArrayList<Transitions.TransitionFinishCallback> mPendingFinishCallbacks =
234                 new ArrayList<>();
235         private int mUsers = 0;
236 
RemoteDeathHandler(IBinder remote)237         RemoteDeathHandler(IBinder remote) {
238             mRemote = remote;
239         }
240 
addUser(@ullable Transitions.TransitionFinishCallback finishCallback)241         void addUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
242             if (finishCallback != null) {
243                 mPendingFinishCallbacks.add(finishCallback);
244             }
245             ++mUsers;
246         }
247 
removeUser(@ullable Transitions.TransitionFinishCallback finishCallback)248         void removeUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
249             if (finishCallback != null) {
250                 mPendingFinishCallbacks.remove(finishCallback);
251             }
252             --mUsers;
253         }
254 
getUserCount()255         int getUserCount() {
256             return mUsers;
257         }
258 
259         @Override
260         @BinderThread
binderDied()261         public void binderDied() {
262             mMainExecutor.execute(() -> {
263                 for (int i = mFilters.size() - 1; i >= 0; --i) {
264                     if (mRemote.equals(mFilters.get(i).second.asBinder())) {
265                         mFilters.remove(i);
266                     }
267                 }
268                 for (int i = mRequestedRemotes.size() - 1; i >= 0; --i) {
269                     if (mRemote.equals(mRequestedRemotes.valueAt(i).asBinder())) {
270                         mRequestedRemotes.removeAt(i);
271                     }
272                 }
273                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
274                     mPendingFinishCallbacks.get(i).onTransitionFinished(
275                             null /* wct */, null /* wctCB */);
276                 }
277                 mPendingFinishCallbacks.clear();
278             });
279         }
280     }
281 }
282