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