1 /* 2 * Copyright (C) 2018 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.server.wm; 18 19 import static com.android.server.wm.AnimationAdapterProto.REMOTE; 20 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; 21 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; 22 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS; 23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 25 26 import android.graphics.Point; 27 import android.graphics.Rect; 28 import android.os.Binder; 29 import android.os.Handler; 30 import android.os.IBinder.DeathRecipient; 31 import android.os.RemoteException; 32 import android.os.SystemClock; 33 import android.util.Slog; 34 import android.util.proto.ProtoOutputStream; 35 import android.view.IRemoteAnimationFinishedCallback; 36 import android.view.RemoteAnimationAdapter; 37 import android.view.RemoteAnimationTarget; 38 import android.view.SurfaceControl; 39 import android.view.SurfaceControl.Transaction; 40 41 import com.android.internal.util.FastPrintWriter; 42 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; 43 import com.android.server.wm.utils.InsetUtils; 44 45 import java.io.PrintWriter; 46 import java.io.StringWriter; 47 import java.util.ArrayList; 48 49 /** 50 * Helper class to run app animations in a remote process. 51 */ 52 class RemoteAnimationController implements DeathRecipient { 53 private static final String TAG = TAG_WITH_CLASS_NAME 54 || (DEBUG_REMOTE_ANIMATIONS && !DEBUG_APP_TRANSITIONS) 55 ? "RemoteAnimationController" : TAG_WM; 56 private static final long TIMEOUT_MS = 2000; 57 58 private final WindowManagerService mService; 59 private final RemoteAnimationAdapter mRemoteAnimationAdapter; 60 private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); 61 private final Rect mTmpRect = new Rect(); 62 private final Handler mHandler; 63 private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); 64 65 private FinishedCallback mFinishedCallback; 66 private boolean mCanceled; 67 private boolean mLinkedToDeathOfRunner; 68 RemoteAnimationController(WindowManagerService service, RemoteAnimationAdapter remoteAnimationAdapter, Handler handler)69 RemoteAnimationController(WindowManagerService service, 70 RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) { 71 mService = service; 72 mRemoteAnimationAdapter = remoteAnimationAdapter; 73 mHandler = handler; 74 } 75 76 /** 77 * Creates an animation for each individual {@link AppWindowToken}. 78 * 79 * @param appWindowToken The app to animate. 80 * @param position The position app bounds, in screen coordinates. 81 * @param stackBounds The stack bounds of the app. 82 * @return The adapter to be run on the app. 83 */ createAnimationAdapter(AppWindowToken appWindowToken, Point position, Rect stackBounds)84 AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position, 85 Rect stackBounds) { 86 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token=" 87 + appWindowToken); 88 final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper( 89 appWindowToken, position, stackBounds); 90 mPendingAnimations.add(adapter); 91 return adapter; 92 } 93 94 /** 95 * Called when the transition is ready to be started, and all leashes have been set up. 96 */ goodToGo()97 void goodToGo() { 98 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo()"); 99 if (mPendingAnimations.isEmpty() || mCanceled) { 100 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): Animation finished already," 101 + " canceled=" + mCanceled 102 + " mPendingAnimations=" + mPendingAnimations.size()); 103 onAnimationFinished(); 104 return; 105 } 106 107 // Scale the timeout with the animator scale the controlling app is using. 108 mHandler.postDelayed(mTimeoutRunnable, 109 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale())); 110 mFinishedCallback = new FinishedCallback(this); 111 112 final RemoteAnimationTarget[] animations = createAnimations(); 113 if (animations.length == 0) { 114 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate"); 115 onAnimationFinished(); 116 return; 117 } 118 mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { 119 try { 120 linkToDeathOfRunner(); 121 mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback); 122 } catch (RemoteException e) { 123 Slog.e(TAG, "Failed to start remote animation", e); 124 onAnimationFinished(); 125 } 126 if (DEBUG_REMOTE_ANIMATIONS) { 127 Slog.d(TAG, "startAnimation(): Notify animation start:"); 128 writeStartDebugStatement(); 129 } 130 }); 131 sendRunningRemoteAnimation(true); 132 } 133 cancelAnimation(String reason)134 private void cancelAnimation(String reason) { 135 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason); 136 synchronized (mService.getWindowManagerLock()) { 137 if (mCanceled) { 138 return; 139 } 140 mCanceled = true; 141 } 142 onAnimationFinished(); 143 invokeAnimationCancelled(); 144 } 145 writeStartDebugStatement()146 private void writeStartDebugStatement() { 147 Slog.i(TAG, "Starting remote animation"); 148 final StringWriter sw = new StringWriter(); 149 final FastPrintWriter pw = new FastPrintWriter(sw); 150 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 151 mPendingAnimations.get(i).dump(pw, ""); 152 } 153 pw.close(); 154 Slog.i(TAG, sw.toString()); 155 } 156 createAnimations()157 private RemoteAnimationTarget[] createAnimations() { 158 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()"); 159 final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); 160 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 161 final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i); 162 final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation(); 163 if (target != null) { 164 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken); 165 targets.add(target); 166 } else { 167 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token=" 168 + wrapper.mAppWindowToken); 169 170 // We can't really start an animation but we still need to make sure to finish the 171 // pending animation that was started by SurfaceAnimator 172 if (wrapper.mCapturedFinishCallback != null) { 173 wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper); 174 } 175 mPendingAnimations.remove(i); 176 } 177 } 178 return targets.toArray(new RemoteAnimationTarget[targets.size()]); 179 } 180 onAnimationFinished()181 private void onAnimationFinished() { 182 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations=" 183 + mPendingAnimations.size()); 184 mHandler.removeCallbacks(mTimeoutRunnable); 185 synchronized (mService.mWindowMap) { 186 unlinkToDeathOfRunner(); 187 releaseFinishedCallback(); 188 mService.openSurfaceTransaction(); 189 try { 190 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, 191 "onAnimationFinished(): Notify animation finished:"); 192 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { 193 final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i); 194 adapter.mCapturedFinishCallback.onAnimationFinished(adapter); 195 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken); 196 } 197 } catch (Exception e) { 198 Slog.e(TAG, "Failed to finish remote animation", e); 199 throw e; 200 } finally { 201 mService.closeSurfaceTransaction("RemoteAnimationController#finished"); 202 } 203 } 204 sendRunningRemoteAnimation(false); 205 if (DEBUG_REMOTE_ANIMATIONS) Slog.i(TAG, "Finishing remote animation"); 206 } 207 invokeAnimationCancelled()208 private void invokeAnimationCancelled() { 209 try { 210 mRemoteAnimationAdapter.getRunner().onAnimationCancelled(); 211 } catch (RemoteException e) { 212 Slog.e(TAG, "Failed to notify cancel", e); 213 } 214 } 215 releaseFinishedCallback()216 private void releaseFinishedCallback() { 217 if (mFinishedCallback != null) { 218 mFinishedCallback.release(); 219 mFinishedCallback = null; 220 } 221 } 222 sendRunningRemoteAnimation(boolean running)223 private void sendRunningRemoteAnimation(boolean running) { 224 final int pid = mRemoteAnimationAdapter.getCallingPid(); 225 if (pid == 0) { 226 throw new RuntimeException("Calling pid of remote animation was null"); 227 } 228 mService.sendSetRunningRemoteAnimation(pid, running); 229 } 230 linkToDeathOfRunner()231 private void linkToDeathOfRunner() throws RemoteException { 232 if (!mLinkedToDeathOfRunner) { 233 mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0); 234 mLinkedToDeathOfRunner = true; 235 } 236 } 237 unlinkToDeathOfRunner()238 private void unlinkToDeathOfRunner() { 239 if (mLinkedToDeathOfRunner) { 240 mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0); 241 mLinkedToDeathOfRunner = false; 242 } 243 } 244 245 @Override binderDied()246 public void binderDied() { 247 cancelAnimation("binderDied"); 248 } 249 250 private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub { 251 252 RemoteAnimationController mOuter; 253 FinishedCallback(RemoteAnimationController outer)254 FinishedCallback(RemoteAnimationController outer) { 255 mOuter = outer; 256 } 257 258 @Override onAnimationFinished()259 public void onAnimationFinished() throws RemoteException { 260 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-onAnimationFinished(): mOuter=" + mOuter); 261 final long token = Binder.clearCallingIdentity(); 262 try { 263 if (mOuter != null) { 264 mOuter.onAnimationFinished(); 265 266 // In case the client holds on to the finish callback, make sure we don't leak 267 // RemoteAnimationController which in turn would leak the runner on the client. 268 mOuter = null; 269 } 270 } finally { 271 Binder.restoreCallingIdentity(token); 272 } 273 } 274 275 /** 276 * Marks this callback as not be used anymore by releasing the reference to the outer class 277 * to prevent memory leak. 278 */ release()279 void release() { 280 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-release(): mOuter=" + mOuter); 281 mOuter = null; 282 } 283 }; 284 285 private class RemoteAnimationAdapterWrapper implements AnimationAdapter { 286 287 private final AppWindowToken mAppWindowToken; 288 private SurfaceControl mCapturedLeash; 289 private OnAnimationFinishedCallback mCapturedFinishCallback; 290 private final Point mPosition = new Point(); 291 private final Rect mStackBounds = new Rect(); 292 private RemoteAnimationTarget mTarget; 293 RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position, Rect stackBounds)294 RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position, 295 Rect stackBounds) { 296 mAppWindowToken = appWindowToken; 297 mPosition.set(position.x, position.y); 298 mStackBounds.set(stackBounds); 299 } 300 createRemoteAppAnimation()301 RemoteAnimationTarget createRemoteAppAnimation() { 302 final Task task = mAppWindowToken.getTask(); 303 final WindowState mainWindow = mAppWindowToken.findMainWindow(); 304 if (task == null || mainWindow == null || mCapturedFinishCallback == null 305 || mCapturedLeash == null) { 306 return null; 307 } 308 final Rect insets = new Rect(mainWindow.mContentInsets); 309 InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets()); 310 mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(), 311 mCapturedLeash, !mAppWindowToken.fillsParent(), 312 mainWindow.mWinAnimator.mLastClipRect, insets, 313 mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds, 314 task.getWindowConfiguration(), false /*isNotInRecents*/); 315 return mTarget; 316 } 317 getMode()318 private int getMode() { 319 if (mService.mOpeningApps.contains(mAppWindowToken)) { 320 return RemoteAnimationTarget.MODE_OPENING; 321 } else { 322 return RemoteAnimationTarget.MODE_CLOSING; 323 } 324 } 325 326 @Override getDetachWallpaper()327 public boolean getDetachWallpaper() { 328 return false; 329 } 330 331 @Override getShowWallpaper()332 public boolean getShowWallpaper() { 333 return false; 334 } 335 336 @Override getBackgroundColor()337 public int getBackgroundColor() { 338 return 0; 339 } 340 341 @Override startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback)342 public void startAnimation(SurfaceControl animationLeash, Transaction t, 343 OnAnimationFinishedCallback finishCallback) { 344 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation"); 345 346 // Restore z-layering, position and stack crop until client has a chance to modify it. 347 t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex()); 348 t.setPosition(animationLeash, mPosition.x, mPosition.y); 349 mTmpRect.set(mStackBounds); 350 mTmpRect.offsetTo(0, 0); 351 t.setWindowCrop(animationLeash, mTmpRect); 352 mCapturedLeash = animationLeash; 353 mCapturedFinishCallback = finishCallback; 354 } 355 356 @Override onAnimationCancelled(SurfaceControl animationLeash)357 public void onAnimationCancelled(SurfaceControl animationLeash) { 358 mPendingAnimations.remove(this); 359 if (mPendingAnimations.isEmpty()) { 360 mHandler.removeCallbacks(mTimeoutRunnable); 361 releaseFinishedCallback(); 362 invokeAnimationCancelled(); 363 sendRunningRemoteAnimation(false); 364 } 365 } 366 367 @Override getDurationHint()368 public long getDurationHint() { 369 return mRemoteAnimationAdapter.getDuration(); 370 } 371 372 @Override getStatusBarTransitionsStartTime()373 public long getStatusBarTransitionsStartTime() { 374 return SystemClock.uptimeMillis() 375 + mRemoteAnimationAdapter.getStatusBarTransitionDelay(); 376 } 377 378 @Override dump(PrintWriter pw, String prefix)379 public void dump(PrintWriter pw, String prefix) { 380 pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken); 381 if (mTarget != null) { 382 pw.print(prefix); pw.println("Target:"); 383 mTarget.dump(pw, prefix + " "); 384 } else { 385 pw.print(prefix); pw.println("Target: null"); 386 } 387 } 388 389 @Override writeToProto(ProtoOutputStream proto)390 public void writeToProto(ProtoOutputStream proto) { 391 final long token = proto.start(REMOTE); 392 if (mTarget != null) { 393 mTarget.writeToProto(proto, TARGET); 394 } 395 proto.end(token); 396 } 397 } 398 } 399