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 static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.IBinder;
24 import android.os.Parcel;
25 import android.os.RemoteException;
26 import android.util.ArrayMap;
27 import android.util.Log;
28 import android.util.Pair;
29 import android.util.Slog;
30 import android.view.SurfaceControl;
31 import android.window.IRemoteTransition;
32 import android.window.IRemoteTransitionFinishedCallback;
33 import android.window.RemoteTransition;
34 import android.window.TransitionFilter;
35 import android.window.TransitionInfo;
36 import android.window.TransitionRequestInfo;
37 import android.window.WindowAnimationState;
38 import android.window.WindowContainerTransaction;
39 
40 import androidx.annotation.BinderThread;
41 
42 import com.android.internal.protolog.common.ProtoLog;
43 import com.android.wm.shell.common.ShellExecutor;
44 import com.android.wm.shell.protolog.ShellProtoLogGroup;
45 import com.android.wm.shell.shared.TransitionUtil;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 
51 /**
52  * Handler that deals with RemoteTransitions. It will only request to handle a transition
53  * if the request includes a specific remote.
54  */
55 public class RemoteTransitionHandler implements Transitions.TransitionHandler {
56     private static final String TAG = "RemoteTransitionHandler";
57 
58     private final ShellExecutor mMainExecutor;
59 
60     /** Includes remotes explicitly requested by, eg, ActivityOptions */
61     private final ArrayMap<IBinder, RemoteTransition> mRequestedRemotes = new ArrayMap<>();
62 
63     /** Ordered by specificity. Last filters will be checked first */
64     private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mFilters =
65             new ArrayList<>();
66     private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mTakeoverFilters =
67             new ArrayList<>();
68 
69     private final ArrayMap<IBinder, RemoteDeathHandler> mDeathHandlers = new ArrayMap<>();
70 
RemoteTransitionHandler(@onNull ShellExecutor mainExecutor)71     RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
72         mMainExecutor = mainExecutor;
73     }
74 
addFiltered(TransitionFilter filter, RemoteTransition remote)75     void addFiltered(TransitionFilter filter, RemoteTransition remote) {
76         handleDeath(remote.asBinder(), null /* finishCallback */);
77         mFilters.add(new Pair<>(filter, remote));
78     }
79 
addFilteredForTakeover(TransitionFilter filter, RemoteTransition remote)80     void addFilteredForTakeover(TransitionFilter filter, RemoteTransition remote) {
81         handleDeath(remote.asBinder(), null /* finishCallback */);
82         mTakeoverFilters.add(new Pair<>(filter, remote));
83     }
84 
removeFiltered(RemoteTransition remote)85     void removeFiltered(RemoteTransition remote) {
86         boolean removed = false;
87         for (ArrayList<Pair<TransitionFilter, RemoteTransition>> filters
88                 : Arrays.asList(mFilters, mTakeoverFilters)) {
89             for (int i = filters.size() - 1; i >= 0; --i) {
90                 if (filters.get(i).second.asBinder().equals(remote.asBinder())) {
91                     filters.remove(i);
92                     removed = true;
93                 }
94             }
95         }
96 
97         if (removed) {
98             unhandleDeath(remote.asBinder(), null /* finishCallback */);
99         }
100     }
101 
102     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)103     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
104             @Nullable SurfaceControl.Transaction finishT) {
105         RemoteTransition remoteTransition = mRequestedRemotes.remove(transition);
106         if (remoteTransition == null) {
107             return;
108         }
109 
110         try {
111             remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted);
112         } catch (RemoteException e) {
113             Log.e(TAG, "Error delegating onTransitionConsumed()", e);
114         }
115     }
116 
117     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)118     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
119             @NonNull SurfaceControl.Transaction startTransaction,
120             @NonNull SurfaceControl.Transaction finishTransaction,
121             @NonNull Transitions.TransitionFinishCallback finishCallback) {
122         if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
123             // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
124             // operations of the start transaction may be ignored.
125             mRequestedRemotes.remove(transition);
126             return false;
127         }
128         RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
129         if (pendingRemote == null) {
130             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition doesn't have "
131                     + "explicit remote, search filters for match for %s", info);
132             // If no explicit remote, search filters until one matches
133             for (int i = mFilters.size() - 1; i >= 0; --i) {
134                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
135                         mFilters.get(i));
136                 if (mFilters.get(i).first.matches(info)) {
137                     Slog.d(TAG, "Found filter" + mFilters.get(i));
138                     pendingRemote = mFilters.get(i).second;
139                     // Add to requested list so that it can be found for merge requests.
140                     mRequestedRemotes.put(transition, pendingRemote);
141                     break;
142                 }
143             }
144         }
145         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
146                 info.getDebugId(), pendingRemote);
147 
148         if (pendingRemote == null) return false;
149 
150         final RemoteTransition remote = pendingRemote;
151         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
152             @Override
153             public void onTransitionFinished(WindowContainerTransaction wct,
154                     SurfaceControl.Transaction sct) {
155                 unhandleDeath(remote.asBinder(), finishCallback);
156                 if (sct != null) {
157                     finishTransaction.merge(sct);
158                 }
159                 mMainExecutor.execute(() -> {
160                     mRequestedRemotes.remove(transition);
161                     finishCallback.onTransitionFinished(wct);
162                 });
163             }
164         };
165         // If the remote is actually in the same process, then make a copy of parameters since
166         // remote impls assume that they have to clean-up native references.
167         final SurfaceControl.Transaction remoteStartT =
168                 copyIfLocal(startTransaction, remote.getRemoteTransition());
169         final TransitionInfo remoteInfo =
170                 remoteStartT == startTransaction ? info : info.localRemoteCopy();
171         try {
172             handleDeath(remote.asBinder(), finishCallback);
173             remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
174             // assume that remote will apply the start transaction.
175             startTransaction.clear();
176             Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
177         } catch (RemoteException e) {
178             Log.e(Transitions.TAG, "Error running remote transition.", e);
179             if (remoteStartT != startTransaction) {
180                 remoteStartT.close();
181             }
182             startTransaction.apply();
183             unhandleDeath(remote.asBinder(), finishCallback);
184             mRequestedRemotes.remove(transition);
185             mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
186         }
187         return true;
188     }
189 
copyIfLocal(SurfaceControl.Transaction t, IRemoteTransition remote)190     static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
191             IRemoteTransition remote) {
192         // We care more about parceling than local (though they should be the same); so, use
193         // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
194         if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
195             // No local interface, so binder itself will parcel and thus we don't need to.
196             return t;
197         }
198         // Binder won't be parceling; however, the remotes assume they have their own native
199         // objects (and don't know if caller is local or not), so we need to make a COPY here so
200         // that the remote can clean it up without clearing the original transaction.
201         // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
202         final Parcel p = Parcel.obtain();
203         try {
204             t.writeToParcel(p, 0);
205             p.setDataPosition(0);
206             return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
207         } finally {
208             p.recycle();
209         }
210     }
211 
212     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)213     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
214             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
215             @NonNull Transitions.TransitionFinishCallback finishCallback) {
216         final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
217         if (remoteTransition == null) return;
218 
219         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "   Merge into remote: %s",
220                 remoteTransition);
221 
222         final IRemoteTransition remote = remoteTransition.getRemoteTransition();
223         if (remote == null) return;
224 
225         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
226             @Override
227             public void onTransitionFinished(WindowContainerTransaction wct,
228                     SurfaceControl.Transaction sct) {
229                 // We have merged, since we sent the transaction over binder, the one in this
230                 // process won't be cleared if the remote applied it. We don't actually know if the
231                 // remote applied the transaction, but applying twice will break surfaceflinger
232                 // so just assume the worst-case and clear the local transaction.
233                 t.clear();
234                 mMainExecutor.execute(() -> {
235                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
236                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
237                                 + "transition it was supposed to merge into). This usually means "
238                                 + "that the mergeTarget's RemoteTransition impl erroneously "
239                                 + "accepted/ran the merge request after finishing the mergeTarget");
240                     }
241                     finishCallback.onTransitionFinished(wct);
242                 });
243             }
244         };
245         try {
246             // If the remote is actually in the same process, then make a copy of parameters since
247             // remote impls assume that they have to clean-up native references.
248             final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
249             final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
250             remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
251         } catch (RemoteException e) {
252             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
253         }
254     }
255 
256     @Nullable
257     @Override
getHandlerForTakeover( @onNull IBinder transition, @NonNull TransitionInfo info)258     public Transitions.TransitionHandler getHandlerForTakeover(
259             @NonNull IBinder transition, @NonNull TransitionInfo info) {
260         if (!returnAnimationFrameworkLibrary()) {
261             return null;
262         }
263 
264         for (Pair<TransitionFilter, RemoteTransition> registered : mTakeoverFilters) {
265             if (registered.first.matches(info)) {
266                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
267                         "Found matching remote to takeover (#%d)", info.getDebugId());
268 
269                 OneShotRemoteHandler oneShot =
270                         new OneShotRemoteHandler(mMainExecutor, registered.second);
271                 oneShot.setTransition(transition);
272                 return oneShot;
273             }
274         }
275 
276         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
277                 "No matching remote found to takeover (#%d)", info.getDebugId());
278         return null;
279     }
280 
281     @Override
takeOverAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction transaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowAnimationState[] states)282     public boolean takeOverAnimation(
283             @NonNull IBinder transition, @NonNull TransitionInfo info,
284             @NonNull SurfaceControl.Transaction transaction,
285             @NonNull Transitions.TransitionFinishCallback finishCallback,
286             @NonNull WindowAnimationState[] states) {
287         Transitions.TransitionHandler handler = getHandlerForTakeover(transition, info);
288         if (handler == null) {
289             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
290                     "Take over request failed: no matching remote for (#%d)", info.getDebugId());
291             return false;
292         }
293         ((OneShotRemoteHandler) handler).setTransition(transition);
294         return handler.takeOverAnimation(transition, info, transaction, finishCallback, states);
295     }
296 
297     @Override
298     @Nullable
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)299     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
300             @Nullable TransitionRequestInfo request) {
301         RemoteTransition remote = request.getRemoteTransition();
302         if (remote == null) return null;
303         mRequestedRemotes.put(transition, remote);
304         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
305                 + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
306         return new WindowContainerTransaction();
307     }
308 
handleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)309     private void handleDeath(@NonNull IBinder remote,
310             @Nullable Transitions.TransitionFinishCallback finishCallback) {
311         synchronized (mDeathHandlers) {
312             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
313             if (deathHandler == null) {
314                 deathHandler = new RemoteDeathHandler(remote);
315                 try {
316                     remote.linkToDeath(deathHandler, 0 /* flags */);
317                 } catch (RemoteException e) {
318                     Slog.e(TAG, "Failed to link to death");
319                     return;
320                 }
321                 mDeathHandlers.put(remote, deathHandler);
322             }
323             deathHandler.addUser(finishCallback);
324         }
325     }
326 
unhandleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)327     private void unhandleDeath(@NonNull IBinder remote,
328             @Nullable Transitions.TransitionFinishCallback finishCallback) {
329         synchronized (mDeathHandlers) {
330             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
331             if (deathHandler == null) return;
332             deathHandler.removeUser(finishCallback);
333             if (deathHandler.getUserCount() == 0) {
334                 if (!deathHandler.mPendingFinishCallbacks.isEmpty()) {
335                     throw new IllegalStateException("Unhandling death for binder that still has"
336                             + " pending finishCallback(s).");
337                 }
338                 remote.unlinkToDeath(deathHandler, 0 /* flags */);
339                 mDeathHandlers.remove(remote);
340             }
341         }
342     }
343 
dump(@onNull PrintWriter pw, String prefix)344     void dump(@NonNull PrintWriter pw, String prefix) {
345         final String innerPrefix = prefix + "  ";
346 
347         pw.println(prefix + "Registered Remotes:");
348         if (mFilters.isEmpty()) {
349             pw.println(innerPrefix + "none");
350         } else {
351             for (Pair<TransitionFilter, RemoteTransition> entry : mFilters) {
352                 dumpRemote(pw, innerPrefix, entry.second);
353             }
354         }
355 
356         pw.println(prefix + "Registered Takeover Remotes:");
357         if (mTakeoverFilters.isEmpty()) {
358             pw.println(innerPrefix + "none");
359         } else {
360             for (Pair<TransitionFilter, RemoteTransition> entry : mTakeoverFilters) {
361                 dumpRemote(pw, innerPrefix, entry.second);
362             }
363         }
364     }
365 
dumpRemote(@onNull PrintWriter pw, String prefix, RemoteTransition remote)366     private void dumpRemote(@NonNull PrintWriter pw, String prefix, RemoteTransition remote) {
367         pw.print(prefix);
368         pw.print(remote.getDebugName());
369         pw.println(" (" + Integer.toHexString(System.identityHashCode(remote)) + ")");
370     }
371 
372     /** NOTE: binder deaths can alter the filter order */
373     private class RemoteDeathHandler implements IBinder.DeathRecipient {
374         private final IBinder mRemote;
375         private final ArrayList<Transitions.TransitionFinishCallback> mPendingFinishCallbacks =
376                 new ArrayList<>();
377         private int mUsers = 0;
378 
RemoteDeathHandler(IBinder remote)379         RemoteDeathHandler(IBinder remote) {
380             mRemote = remote;
381         }
382 
addUser(@ullable Transitions.TransitionFinishCallback finishCallback)383         void addUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
384             if (finishCallback != null) {
385                 mPendingFinishCallbacks.add(finishCallback);
386             }
387             ++mUsers;
388         }
389 
removeUser(@ullable Transitions.TransitionFinishCallback finishCallback)390         void removeUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
391             if (finishCallback != null) {
392                 mPendingFinishCallbacks.remove(finishCallback);
393             }
394             --mUsers;
395         }
396 
getUserCount()397         int getUserCount() {
398             return mUsers;
399         }
400 
401         @Override
402         @BinderThread
binderDied()403         public void binderDied() {
404             mMainExecutor.execute(() -> {
405                 for (int i = mFilters.size() - 1; i >= 0; --i) {
406                     if (mRemote.equals(mFilters.get(i).second.asBinder())) {
407                         mFilters.remove(i);
408                     }
409                 }
410                 for (int i = mRequestedRemotes.size() - 1; i >= 0; --i) {
411                     if (mRemote.equals(mRequestedRemotes.valueAt(i).asBinder())) {
412                         mRequestedRemotes.removeAt(i);
413                     }
414                 }
415                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
416                     mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */);
417                 }
418                 mPendingFinishCallbacks.clear();
419             });
420         }
421     }
422 }
423