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