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<RemoteAnimationRecord> 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 record 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 relative to position.
82      * @param startBounds The stack bounds before the transition, in screen coordinates
83      * @return The record representing animation(s) to run on the app.
84      */
createRemoteAnimationRecord(AppWindowToken appWindowToken, Point position, Rect stackBounds, Rect startBounds)85     RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken,
86             Point position, Rect stackBounds, Rect startBounds) {
87         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token="
88                 + appWindowToken);
89         final RemoteAnimationRecord adapters =
90                 new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds);
91         mPendingAnimations.add(adapters);
92         return adapters;
93     }
94 
95     /**
96      * Called when the transition is ready to be started, and all leashes have been set up.
97      */
goodToGo()98     void goodToGo() {
99         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo()");
100         if (mPendingAnimations.isEmpty() || mCanceled) {
101             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): Animation finished already,"
102                     + " canceled=" + mCanceled
103                     + " mPendingAnimations=" + mPendingAnimations.size());
104             onAnimationFinished();
105             return;
106         }
107 
108         // Scale the timeout with the animator scale the controlling app is using.
109         mHandler.postDelayed(mTimeoutRunnable,
110                 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
111         mFinishedCallback = new FinishedCallback(this);
112 
113         final RemoteAnimationTarget[] animations = createAnimations();
114         if (animations.length == 0) {
115             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate");
116             onAnimationFinished();
117             return;
118         }
119         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
120             try {
121                 linkToDeathOfRunner();
122                 mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback);
123             } catch (RemoteException e) {
124                 Slog.e(TAG, "Failed to start remote animation", e);
125                 onAnimationFinished();
126             }
127             if (DEBUG_REMOTE_ANIMATIONS) {
128                 Slog.d(TAG, "startAnimation(): Notify animation start:");
129                 writeStartDebugStatement();
130             }
131         });
132         sendRunningRemoteAnimation(true);
133     }
134 
cancelAnimation(String reason)135     void cancelAnimation(String reason) {
136         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason);
137         synchronized (mService.getWindowManagerLock()) {
138             if (mCanceled) {
139                 return;
140             }
141             mCanceled = true;
142         }
143         onAnimationFinished();
144         invokeAnimationCancelled();
145     }
146 
writeStartDebugStatement()147     private void writeStartDebugStatement() {
148         Slog.i(TAG, "Starting remote animation");
149         final StringWriter sw = new StringWriter();
150         final FastPrintWriter pw = new FastPrintWriter(sw);
151         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
152             mPendingAnimations.get(i).mAdapter.dump(pw, "");
153         }
154         pw.close();
155         Slog.i(TAG, sw.toString());
156     }
157 
createAnimations()158     private RemoteAnimationTarget[] createAnimations() {
159         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
160         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
161         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
162             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
163             final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
164             if (target != null) {
165                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken);
166                 targets.add(target);
167             } else {
168                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token="
169                         + wrappers.mAppWindowToken);
170 
171                 // We can't really start an animation but we still need to make sure to finish the
172                 // pending animation that was started by SurfaceAnimator
173                 if (wrappers.mAdapter != null
174                         && wrappers.mAdapter.mCapturedFinishCallback != null) {
175                     wrappers.mAdapter.mCapturedFinishCallback
176                             .onAnimationFinished(wrappers.mAdapter);
177                 }
178                 if (wrappers.mThumbnailAdapter != null
179                         && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
180                     wrappers.mThumbnailAdapter.mCapturedFinishCallback
181                             .onAnimationFinished(wrappers.mThumbnailAdapter);
182                 }
183                 mPendingAnimations.remove(i);
184             }
185         }
186         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
187     }
188 
onAnimationFinished()189     private void onAnimationFinished() {
190         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations="
191                 + mPendingAnimations.size());
192         mHandler.removeCallbacks(mTimeoutRunnable);
193         synchronized (mService.mGlobalLock) {
194             unlinkToDeathOfRunner();
195             releaseFinishedCallback();
196             mService.openSurfaceTransaction();
197             try {
198                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG,
199                         "onAnimationFinished(): Notify animation finished:");
200                 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
201                     final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
202                     if (adapters.mAdapter != null) {
203                         adapters.mAdapter.mCapturedFinishCallback
204                                 .onAnimationFinished(adapters.mAdapter);
205                     }
206                     if (adapters.mThumbnailAdapter != null) {
207                         adapters.mThumbnailAdapter.mCapturedFinishCallback
208                                 .onAnimationFinished(adapters.mThumbnailAdapter);
209                     }
210                     if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
211                 }
212             } catch (Exception e) {
213                 Slog.e(TAG, "Failed to finish remote animation", e);
214                 throw e;
215             } finally {
216                 mService.closeSurfaceTransaction("RemoteAnimationController#finished");
217             }
218         }
219         sendRunningRemoteAnimation(false);
220         if (DEBUG_REMOTE_ANIMATIONS) Slog.i(TAG, "Finishing remote animation");
221     }
222 
invokeAnimationCancelled()223     private void invokeAnimationCancelled() {
224         try {
225             mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
226         } catch (RemoteException e) {
227             Slog.e(TAG, "Failed to notify cancel", e);
228         }
229     }
230 
releaseFinishedCallback()231     private void releaseFinishedCallback() {
232         if (mFinishedCallback != null) {
233             mFinishedCallback.release();
234             mFinishedCallback = null;
235         }
236     }
237 
sendRunningRemoteAnimation(boolean running)238     private void sendRunningRemoteAnimation(boolean running) {
239         final int pid = mRemoteAnimationAdapter.getCallingPid();
240         if (pid == 0) {
241             throw new RuntimeException("Calling pid of remote animation was null");
242         }
243         mService.sendSetRunningRemoteAnimation(pid, running);
244     }
245 
linkToDeathOfRunner()246     private void linkToDeathOfRunner() throws RemoteException {
247         if (!mLinkedToDeathOfRunner) {
248             mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0);
249             mLinkedToDeathOfRunner = true;
250         }
251     }
252 
unlinkToDeathOfRunner()253     private void unlinkToDeathOfRunner() {
254         if (mLinkedToDeathOfRunner) {
255             mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0);
256             mLinkedToDeathOfRunner = false;
257         }
258     }
259 
260     @Override
binderDied()261     public void binderDied() {
262         cancelAnimation("binderDied");
263     }
264 
265     private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
266 
267         RemoteAnimationController mOuter;
268 
FinishedCallback(RemoteAnimationController outer)269         FinishedCallback(RemoteAnimationController outer) {
270             mOuter = outer;
271         }
272 
273         @Override
onAnimationFinished()274         public void onAnimationFinished() throws RemoteException {
275             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-onAnimationFinished(): mOuter=" + mOuter);
276             final long token = Binder.clearCallingIdentity();
277             try {
278                 if (mOuter != null) {
279                     mOuter.onAnimationFinished();
280 
281                     // In case the client holds on to the finish callback, make sure we don't leak
282                     // RemoteAnimationController which in turn would leak the runner on the client.
283                     mOuter = null;
284                 }
285             } finally {
286                 Binder.restoreCallingIdentity(token);
287             }
288         }
289 
290         /**
291          * Marks this callback as not be used anymore by releasing the reference to the outer class
292          * to prevent memory leak.
293          */
release()294         void release() {
295             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-release(): mOuter=" + mOuter);
296             mOuter = null;
297         }
298     };
299 
300     /**
301      * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
302      * potentially, multiple animating surfaces (AdapterWrappers) associated with one
303      * Window/Transition. For example, a change transition has an adapter controller for the
304      * main window and an adapter controlling the start-state snapshot.
305      * <p>
306      * This can be thought of as a bridge between the information that the remote animator sees (via
307      * {@link RemoteAnimationTarget}) and what the server sees (the
308      * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
309      */
310     public class RemoteAnimationRecord {
311         RemoteAnimationAdapterWrapper mAdapter;
312         RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
313         RemoteAnimationTarget mTarget;
314         final AppWindowToken mAppWindowToken;
315         final Rect mStartBounds;
316 
RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds, Rect startBounds)317         RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds,
318                 Rect startBounds) {
319             mAppWindowToken = appWindowToken;
320             mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
321             if (startBounds != null) {
322                 mStartBounds = new Rect(startBounds);
323                 mTmpRect.set(startBounds);
324                 mTmpRect.offsetTo(0, 0);
325                 if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
326                     mThumbnailAdapter =
327                             new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
328                 }
329             } else {
330                 mStartBounds = null;
331             }
332         }
333 
createRemoteAnimationTarget()334         RemoteAnimationTarget createRemoteAnimationTarget() {
335             final Task task = mAppWindowToken.getTask();
336             final WindowState mainWindow = mAppWindowToken.findMainWindow();
337             if (task == null || mainWindow == null || mAdapter == null
338                     || mAdapter.mCapturedFinishCallback == null
339                     || mAdapter.mCapturedLeash == null) {
340                 return null;
341             }
342             final Rect insets = new Rect();
343             mainWindow.getContentInsets(insets);
344             InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets());
345             mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
346                     mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(),
347                     mainWindow.mWinAnimator.mLastClipRect, insets,
348                     mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition,
349                     mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
350                     mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
351                     mStartBounds);
352             return mTarget;
353         }
354 
getMode()355         private int getMode() {
356             final DisplayContent dc = mAppWindowToken.getDisplayContent();
357             if (dc.mOpeningApps.contains(mAppWindowToken)) {
358                 return RemoteAnimationTarget.MODE_OPENING;
359             } else if (dc.mChangingApps.contains(mAppWindowToken)) {
360                 return RemoteAnimationTarget.MODE_CHANGING;
361             } else {
362                 return RemoteAnimationTarget.MODE_CLOSING;
363             }
364         }
365     }
366 
367     private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
368         private final RemoteAnimationRecord mRecord;
369         SurfaceControl mCapturedLeash;
370         private OnAnimationFinishedCallback mCapturedFinishCallback;
371         private final Point mPosition = new Point();
372         private final Rect mStackBounds = new Rect();
373 
RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, Rect stackBounds)374         RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
375                 Rect stackBounds) {
376             mRecord = record;
377             mPosition.set(position.x, position.y);
378             mStackBounds.set(stackBounds);
379         }
380 
381         @Override
getShowWallpaper()382         public boolean getShowWallpaper() {
383             return false;
384         }
385 
386         @Override
getBackgroundColor()387         public int getBackgroundColor() {
388             return 0;
389         }
390 
391         @Override
startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback)392         public void startAnimation(SurfaceControl animationLeash, Transaction t,
393                 OnAnimationFinishedCallback finishCallback) {
394             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
395 
396             // Restore z-layering, position and stack crop until client has a chance to modify it.
397             t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex());
398             t.setPosition(animationLeash, mPosition.x, mPosition.y);
399             mTmpRect.set(mStackBounds);
400             mTmpRect.offsetTo(0, 0);
401             t.setWindowCrop(animationLeash, mTmpRect);
402             mCapturedLeash = animationLeash;
403             mCapturedFinishCallback = finishCallback;
404         }
405 
406         @Override
onAnimationCancelled(SurfaceControl animationLeash)407         public void onAnimationCancelled(SurfaceControl animationLeash) {
408             if (mRecord.mAdapter == this) {
409                 mRecord.mAdapter = null;
410             } else {
411                 mRecord.mThumbnailAdapter = null;
412             }
413             if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
414                 mPendingAnimations.remove(mRecord);
415             }
416             if (mPendingAnimations.isEmpty()) {
417                 mHandler.removeCallbacks(mTimeoutRunnable);
418                 releaseFinishedCallback();
419                 invokeAnimationCancelled();
420                 sendRunningRemoteAnimation(false);
421             }
422         }
423 
424         @Override
getDurationHint()425         public long getDurationHint() {
426             return mRemoteAnimationAdapter.getDuration();
427         }
428 
429         @Override
getStatusBarTransitionsStartTime()430         public long getStatusBarTransitionsStartTime() {
431             return SystemClock.uptimeMillis()
432                     + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
433         }
434 
435         @Override
dump(PrintWriter pw, String prefix)436         public void dump(PrintWriter pw, String prefix) {
437             pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken);
438             if (mRecord.mTarget != null) {
439                 pw.print(prefix); pw.println("Target:");
440                 mRecord.mTarget.dump(pw, prefix + "  ");
441             } else {
442                 pw.print(prefix); pw.println("Target: null");
443             }
444         }
445 
446         @Override
writeToProto(ProtoOutputStream proto)447         public void writeToProto(ProtoOutputStream proto) {
448             final long token = proto.start(REMOTE);
449             if (mRecord.mTarget != null) {
450                 mRecord.mTarget.writeToProto(proto, TARGET);
451             }
452             proto.end(token);
453         }
454     }
455 }
456