• 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.IBinder;
22 import android.os.Parcel;
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
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)86     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
87             @Nullable SurfaceControl.Transaction finishT) {
88         mRequestedRemotes.remove(transition);
89     }
90 
91     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)92     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
93             @NonNull SurfaceControl.Transaction startTransaction,
94             @NonNull SurfaceControl.Transaction finishTransaction,
95             @NonNull Transitions.TransitionFinishCallback finishCallback) {
96         RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
97         if (pendingRemote == null) {
98             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
99                     + "explicit remote, search filters for match for %s", transition, info);
100             // If no explicit remote, search filters until one matches
101             for (int i = mFilters.size() - 1; i >= 0; --i) {
102                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
103                         mFilters.get(i));
104                 if (mFilters.get(i).first.matches(info)) {
105                     Slog.d(TAG, "Found filter" + mFilters.get(i));
106                     pendingRemote = mFilters.get(i).second;
107                     // Add to requested list so that it can be found for merge requests.
108                     mRequestedRemotes.put(transition, pendingRemote);
109                     break;
110                 }
111             }
112         }
113         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
114                 transition, pendingRemote);
115 
116         if (pendingRemote == null) return false;
117 
118         final RemoteTransition remote = pendingRemote;
119         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
120             @Override
121             public void onTransitionFinished(WindowContainerTransaction wct,
122                     SurfaceControl.Transaction sct) {
123                 unhandleDeath(remote.asBinder(), finishCallback);
124                 if (sct != null) {
125                     finishTransaction.merge(sct);
126                 }
127                 mMainExecutor.execute(() -> {
128                     mRequestedRemotes.remove(transition);
129                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
130                 });
131             }
132         };
133         Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
134         try {
135             // If the remote is actually in the same process, then make a copy of parameters since
136             // remote impls assume that they have to clean-up native references.
137             final SurfaceControl.Transaction remoteStartT =
138                     copyIfLocal(startTransaction, remote.getRemoteTransition());
139             final TransitionInfo remoteInfo =
140                     remoteStartT == startTransaction ? info : info.localRemoteCopy();
141             handleDeath(remote.asBinder(), finishCallback);
142             remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
143             // assume that remote will apply the start transaction.
144             startTransaction.clear();
145         } catch (RemoteException e) {
146             Log.e(Transitions.TAG, "Error running remote transition.", e);
147             unhandleDeath(remote.asBinder(), finishCallback);
148             mRequestedRemotes.remove(transition);
149             mMainExecutor.execute(
150                     () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
151         }
152         return true;
153     }
154 
copyIfLocal(SurfaceControl.Transaction t, IRemoteTransition remote)155     static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
156             IRemoteTransition remote) {
157         // We care more about parceling than local (though they should be the same); so, use
158         // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
159         if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
160             // No local interface, so binder itself will parcel and thus we don't need to.
161             return t;
162         }
163         // Binder won't be parceling; however, the remotes assume they have their own native
164         // objects (and don't know if caller is local or not), so we need to make a COPY here so
165         // that the remote can clean it up without clearing the original transaction.
166         // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
167         final Parcel p = Parcel.obtain();
168         try {
169             t.writeToParcel(p, 0);
170             p.setDataPosition(0);
171             return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
172         } finally {
173             p.recycle();
174         }
175     }
176 
177     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)178     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
179             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
180             @NonNull Transitions.TransitionFinishCallback finishCallback) {
181         final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
182         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
183                 transition, remote);
184         if (remote == null) return;
185 
186         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
187             @Override
188             public void onTransitionFinished(WindowContainerTransaction wct,
189                     SurfaceControl.Transaction sct) {
190                 // We have merged, since we sent the transaction over binder, the one in this
191                 // process won't be cleared if the remote applied it. We don't actually know if the
192                 // remote applied the transaction, but applying twice will break surfaceflinger
193                 // so just assume the worst-case and clear the local transaction.
194                 t.clear();
195                 mMainExecutor.execute(() -> {
196                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
197                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
198                                 + "transition it was supposed to merge into). This usually means "
199                                 + "that the mergeTarget's RemoteTransition impl erroneously "
200                                 + "accepted/ran the merge request after finishing the mergeTarget");
201                     }
202                     finishCallback.onTransitionFinished(wct, null /* wctCB */);
203                 });
204             }
205         };
206         try {
207             // If the remote is actually in the same process, then make a copy of parameters since
208             // remote impls assume that they have to clean-up native references.
209             final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
210             final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
211             remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
212         } catch (RemoteException e) {
213             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
214         }
215     }
216 
217     @Override
218     @Nullable
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)219     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
220             @Nullable TransitionRequestInfo request) {
221         RemoteTransition remote = request.getRemoteTransition();
222         if (remote == null) return null;
223         mRequestedRemotes.put(transition, remote);
224         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
225                 + " for %s: %s", transition, remote);
226         return new WindowContainerTransaction();
227     }
228 
handleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)229     private void handleDeath(@NonNull IBinder remote,
230             @Nullable Transitions.TransitionFinishCallback finishCallback) {
231         synchronized (mDeathHandlers) {
232             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
233             if (deathHandler == null) {
234                 deathHandler = new RemoteDeathHandler(remote);
235                 try {
236                     remote.linkToDeath(deathHandler, 0 /* flags */);
237                 } catch (RemoteException e) {
238                     Slog.e(TAG, "Failed to link to death");
239                     return;
240                 }
241                 mDeathHandlers.put(remote, deathHandler);
242             }
243             deathHandler.addUser(finishCallback);
244         }
245     }
246 
unhandleDeath(@onNull IBinder remote, @Nullable Transitions.TransitionFinishCallback finishCallback)247     private void unhandleDeath(@NonNull IBinder remote,
248             @Nullable Transitions.TransitionFinishCallback finishCallback) {
249         synchronized (mDeathHandlers) {
250             RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
251             if (deathHandler == null) return;
252             deathHandler.removeUser(finishCallback);
253             if (deathHandler.getUserCount() == 0) {
254                 if (!deathHandler.mPendingFinishCallbacks.isEmpty()) {
255                     throw new IllegalStateException("Unhandling death for binder that still has"
256                             + " pending finishCallback(s).");
257                 }
258                 remote.unlinkToDeath(deathHandler, 0 /* flags */);
259                 mDeathHandlers.remove(remote);
260             }
261         }
262     }
263 
264     /** NOTE: binder deaths can alter the filter order */
265     private class RemoteDeathHandler implements IBinder.DeathRecipient {
266         private final IBinder mRemote;
267         private final ArrayList<Transitions.TransitionFinishCallback> mPendingFinishCallbacks =
268                 new ArrayList<>();
269         private int mUsers = 0;
270 
RemoteDeathHandler(IBinder remote)271         RemoteDeathHandler(IBinder remote) {
272             mRemote = remote;
273         }
274 
addUser(@ullable Transitions.TransitionFinishCallback finishCallback)275         void addUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
276             if (finishCallback != null) {
277                 mPendingFinishCallbacks.add(finishCallback);
278             }
279             ++mUsers;
280         }
281 
removeUser(@ullable Transitions.TransitionFinishCallback finishCallback)282         void removeUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
283             if (finishCallback != null) {
284                 mPendingFinishCallbacks.remove(finishCallback);
285             }
286             --mUsers;
287         }
288 
getUserCount()289         int getUserCount() {
290             return mUsers;
291         }
292 
293         @Override
294         @BinderThread
binderDied()295         public void binderDied() {
296             mMainExecutor.execute(() -> {
297                 for (int i = mFilters.size() - 1; i >= 0; --i) {
298                     if (mRemote.equals(mFilters.get(i).second.asBinder())) {
299                         mFilters.remove(i);
300                     }
301                 }
302                 for (int i = mRequestedRemotes.size() - 1; i >= 0; --i) {
303                     if (mRemote.equals(mRequestedRemotes.valueAt(i).asBinder())) {
304                         mRequestedRemotes.removeAt(i);
305                     }
306                 }
307                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
308                     mPendingFinishCallbacks.get(i).onTransitionFinished(
309                             null /* wct */, null /* wctCB */);
310                 }
311                 mPendingFinishCallbacks.clear();
312             });
313         }
314     }
315 }
316