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