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