• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.returnAnimationFrameworkLongLived;
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.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 startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)213     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
214             @NonNull SurfaceControl.Transaction startT,
215             @NonNull SurfaceControl.Transaction finishT,
216             @NonNull IBinder mergeTarget,
217             @NonNull Transitions.TransitionFinishCallback finishCallback) {
218         final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
219         if (remoteTransition == null) return;
220 
221         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "   Merge into remote: %s",
222                 remoteTransition);
223 
224         final IRemoteTransition remote = remoteTransition.getRemoteTransition();
225         if (remote == null) return;
226 
227         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
228             @Override
229             public void onTransitionFinished(WindowContainerTransaction wct,
230                     SurfaceControl.Transaction sct) {
231                 // We have merged, since we sent the transaction over binder, the one in this
232                 // process won't be cleared if the remote applied it. We don't actually know if the
233                 // remote applied the transaction, but applying twice will break surfaceflinger
234                 // so just assume the worst-case and clear the local transaction.
235                 startT.clear();
236                 mMainExecutor.execute(() -> {
237                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
238                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
239                                 + "transition it was supposed to merge into). This usually means "
240                                 + "that the mergeTarget's RemoteTransition impl erroneously "
241                                 + "accepted/ran the merge request after finishing the mergeTarget");
242                     }
243                     finishCallback.onTransitionFinished(wct);
244                 });
245             }
246         };
247         try {
248             // If the remote is actually in the same process, then make a copy of parameters since
249             // remote impls assume that they have to clean-up native references.
250             final SurfaceControl.Transaction remoteT = copyIfLocal(startT, remote);
251             final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
252             remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
253         } catch (RemoteException e) {
254             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
255         }
256     }
257 
258     @Nullable
259     @Override
getHandlerForTakeover( @onNull IBinder transition, @NonNull TransitionInfo info)260     public Transitions.TransitionHandler getHandlerForTakeover(
261             @NonNull IBinder transition, @NonNull TransitionInfo info) {
262         if (!returnAnimationFrameworkLongLived()) {
263             return null;
264         }
265 
266         for (Pair<TransitionFilter, RemoteTransition> registered : mTakeoverFilters) {
267             if (registered.first.matches(info)) {
268                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
269                         "Found matching remote to takeover (#%d)", info.getDebugId());
270 
271                 OneShotRemoteHandler oneShot =
272                         new OneShotRemoteHandler(mMainExecutor, registered.second);
273                 oneShot.setTransition(transition);
274                 return oneShot;
275             }
276         }
277 
278         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
279                 "No matching remote found to takeover (#%d)", info.getDebugId());
280         return null;
281     }
282 
283     @Override
takeOverAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction transaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowAnimationState[] states)284     public boolean takeOverAnimation(
285             @NonNull IBinder transition, @NonNull TransitionInfo info,
286             @NonNull SurfaceControl.Transaction transaction,
287             @NonNull Transitions.TransitionFinishCallback finishCallback,
288             @NonNull WindowAnimationState[] states) {
289         Transitions.TransitionHandler handler = getHandlerForTakeover(transition, info);
290         if (handler == null) {
291             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
292                     "Take over request failed: no matching remote for (#%d)", info.getDebugId());
293             return false;
294         }
295         ((OneShotRemoteHandler) handler).setTransition(transition);
296         return handler.takeOverAnimation(transition, info, transaction, finishCallback, states);
297     }
298 
299     @Override
300     @Nullable
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)301     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
302             @Nullable TransitionRequestInfo request) {
303         RemoteTransition remote = request.getRemoteTransition();
304         if (remote == null) return null;
305         mRequestedRemotes.put(transition, remote);
306         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
307                 + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
308         return new WindowContainerTransaction();
309     }
310 
handleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)311     private void handleDeath(@NonNull IBinder remote,
312             @Nullable Transitions.TransitionFinishCallback finishCallback) {
313         synchronized (mDeathHandlers) {
314             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
315             if (deathHandler == null) {
316                 deathHandler = new RemoteDeathHandler(remote);
317                 try {
318                     remote.linkToDeath(deathHandler, 0 /* flags */);
319                 } catch (RemoteException e) {
320                     Slog.e(TAG, "Failed to link to death");
321                     return;
322                 }
323                 mDeathHandlers.put(remote, deathHandler);
324             }
325             deathHandler.addUser(finishCallback);
326         }
327     }
328 
unhandleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)329     private void unhandleDeath(@NonNull IBinder remote,
330             @Nullable Transitions.TransitionFinishCallback finishCallback) {
331         synchronized (mDeathHandlers) {
332             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
333             if (deathHandler == null) return;
334             deathHandler.removeUser(finishCallback);
335             if (deathHandler.getUserCount() == 0) {
336                 if (!deathHandler.mPendingFinishCallbacks.isEmpty()) {
337                     throw new IllegalStateException("Unhandling death for binder that still has"
338                             + " pending finishCallback(s).");
339                 }
340                 remote.unlinkToDeath(deathHandler, 0 /* flags */);
341                 mDeathHandlers.remove(remote);
342             }
343         }
344     }
345 
dump(@onNull PrintWriter pw, String prefix)346     void dump(@NonNull PrintWriter pw, String prefix) {
347         final String innerPrefix = prefix + "  ";
348 
349         pw.println(prefix + "Registered Remotes:");
350         if (mFilters.isEmpty()) {
351             pw.println(innerPrefix + "none");
352         } else {
353             for (Pair<TransitionFilter, RemoteTransition> entry : mFilters) {
354                 dumpRemote(pw, innerPrefix, entry.second);
355             }
356         }
357 
358         pw.println(prefix + "Registered Takeover Remotes:");
359         if (mTakeoverFilters.isEmpty()) {
360             pw.println(innerPrefix + "none");
361         } else {
362             for (Pair<TransitionFilter, RemoteTransition> entry : mTakeoverFilters) {
363                 dumpRemote(pw, innerPrefix, entry.second);
364             }
365         }
366     }
367 
dumpRemote(@onNull PrintWriter pw, String prefix, RemoteTransition remote)368     private void dumpRemote(@NonNull PrintWriter pw, String prefix, RemoteTransition remote) {
369         pw.print(prefix);
370         pw.print(remote.getDebugName());
371         pw.println(" (" + Integer.toHexString(System.identityHashCode(remote)) + ")");
372     }
373 
374     /** NOTE: binder deaths can alter the filter order */
375     private class RemoteDeathHandler implements IBinder.DeathRecipient {
376         private final IBinder mRemote;
377         private final ArrayList<Transitions.TransitionFinishCallback> mPendingFinishCallbacks =
378                 new ArrayList<>();
379         private int mUsers = 0;
380 
RemoteDeathHandler(IBinder remote)381         RemoteDeathHandler(IBinder remote) {
382             mRemote = remote;
383         }
384 
addUser(@ullable Transitions.TransitionFinishCallback finishCallback)385         void addUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
386             if (finishCallback != null) {
387                 mPendingFinishCallbacks.add(finishCallback);
388             }
389             ++mUsers;
390         }
391 
removeUser(@ullable Transitions.TransitionFinishCallback finishCallback)392         void removeUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
393             if (finishCallback != null) {
394                 mPendingFinishCallbacks.remove(finishCallback);
395             }
396             --mUsers;
397         }
398 
getUserCount()399         int getUserCount() {
400             return mUsers;
401         }
402 
403         @Override
404         @BinderThread
binderDied()405         public void binderDied() {
406             mMainExecutor.execute(() -> {
407                 for (int i = mFilters.size() - 1; i >= 0; --i) {
408                     if (mRemote.equals(mFilters.get(i).second.asBinder())) {
409                         mFilters.remove(i);
410                     }
411                 }
412                 for (int i = mRequestedRemotes.size() - 1; i >= 0; --i) {
413                     if (mRemote.equals(mRequestedRemotes.valueAt(i).asBinder())) {
414                         mRequestedRemotes.removeAt(i);
415                     }
416                 }
417                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
418                     mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */);
419                 }
420                 mPendingFinishCallbacks.clear();
421             });
422         }
423     }
424 }
425