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         setRunningRemoteAnimation(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         setRunningRemoteAnimation(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 
setRunningRemoteAnimation(boolean running)238     private void setRunningRemoteAnimation(boolean running) {
239         final int pid = mRemoteAnimationAdapter.getCallingPid();
240         final int uid = mRemoteAnimationAdapter.getCallingUid();
241         if (pid == 0) {
242             throw new RuntimeException("Calling pid of remote animation was null");
243         }
244         final WindowProcessController wpc = mService.mAtmService.getProcessController(pid, uid);
245         if (wpc == null) {
246             Slog.w(TAG, "Unable to find process with pid=" + pid + " uid=" + uid);
247             return;
248         }
249         wpc.setRunningRemoteAnimation(running);
250     }
251 
linkToDeathOfRunner()252     private void linkToDeathOfRunner() throws RemoteException {
253         if (!mLinkedToDeathOfRunner) {
254             mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0);
255             mLinkedToDeathOfRunner = true;
256         }
257     }
258 
unlinkToDeathOfRunner()259     private void unlinkToDeathOfRunner() {
260         if (mLinkedToDeathOfRunner) {
261             mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0);
262             mLinkedToDeathOfRunner = false;
263         }
264     }
265 
266     @Override
binderDied()267     public void binderDied() {
268         cancelAnimation("binderDied");
269     }
270 
271     private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
272 
273         RemoteAnimationController mOuter;
274 
FinishedCallback(RemoteAnimationController outer)275         FinishedCallback(RemoteAnimationController outer) {
276             mOuter = outer;
277         }
278 
279         @Override
onAnimationFinished()280         public void onAnimationFinished() throws RemoteException {
281             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-onAnimationFinished(): mOuter=" + mOuter);
282             final long token = Binder.clearCallingIdentity();
283             try {
284                 if (mOuter != null) {
285                     mOuter.onAnimationFinished();
286 
287                     // In case the client holds on to the finish callback, make sure we don't leak
288                     // RemoteAnimationController which in turn would leak the runner on the client.
289                     mOuter = null;
290                 }
291             } finally {
292                 Binder.restoreCallingIdentity(token);
293             }
294         }
295 
296         /**
297          * Marks this callback as not be used anymore by releasing the reference to the outer class
298          * to prevent memory leak.
299          */
release()300         void release() {
301             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-release(): mOuter=" + mOuter);
302             mOuter = null;
303         }
304     };
305 
306     /**
307      * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
308      * potentially, multiple animating surfaces (AdapterWrappers) associated with one
309      * Window/Transition. For example, a change transition has an adapter controller for the
310      * main window and an adapter controlling the start-state snapshot.
311      * <p>
312      * This can be thought of as a bridge between the information that the remote animator sees (via
313      * {@link RemoteAnimationTarget}) and what the server sees (the
314      * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
315      */
316     public class RemoteAnimationRecord {
317         RemoteAnimationAdapterWrapper mAdapter;
318         RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
319         RemoteAnimationTarget mTarget;
320         final AppWindowToken mAppWindowToken;
321         final Rect mStartBounds;
322 
RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds, Rect startBounds)323         RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds,
324                 Rect startBounds) {
325             mAppWindowToken = appWindowToken;
326             mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
327             if (startBounds != null) {
328                 mStartBounds = new Rect(startBounds);
329                 mTmpRect.set(startBounds);
330                 mTmpRect.offsetTo(0, 0);
331                 if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
332                     mThumbnailAdapter =
333                             new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
334                 }
335             } else {
336                 mStartBounds = null;
337             }
338         }
339 
createRemoteAnimationTarget()340         RemoteAnimationTarget createRemoteAnimationTarget() {
341             final Task task = mAppWindowToken.getTask();
342             final WindowState mainWindow = mAppWindowToken.findMainWindow();
343             if (task == null || mainWindow == null || mAdapter == null
344                     || mAdapter.mCapturedFinishCallback == null
345                     || mAdapter.mCapturedLeash == null) {
346                 return null;
347             }
348             final Rect insets = new Rect();
349             mainWindow.getContentInsets(insets);
350             InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets());
351             mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
352                     mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(),
353                     mainWindow.mWinAnimator.mLastClipRect, insets,
354                     mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition,
355                     mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
356                     mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
357                     mStartBounds);
358             return mTarget;
359         }
360 
getMode()361         private int getMode() {
362             final DisplayContent dc = mAppWindowToken.getDisplayContent();
363             if (dc.mOpeningApps.contains(mAppWindowToken)) {
364                 return RemoteAnimationTarget.MODE_OPENING;
365             } else if (dc.mChangingApps.contains(mAppWindowToken)) {
366                 return RemoteAnimationTarget.MODE_CHANGING;
367             } else {
368                 return RemoteAnimationTarget.MODE_CLOSING;
369             }
370         }
371     }
372 
373     private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
374         private final RemoteAnimationRecord mRecord;
375         SurfaceControl mCapturedLeash;
376         private OnAnimationFinishedCallback mCapturedFinishCallback;
377         private final Point mPosition = new Point();
378         private final Rect mStackBounds = new Rect();
379 
RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, Rect stackBounds)380         RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
381                 Rect stackBounds) {
382             mRecord = record;
383             mPosition.set(position.x, position.y);
384             mStackBounds.set(stackBounds);
385         }
386 
387         @Override
getShowWallpaper()388         public boolean getShowWallpaper() {
389             return false;
390         }
391 
392         @Override
getBackgroundColor()393         public int getBackgroundColor() {
394             return 0;
395         }
396 
397         @Override
startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback)398         public void startAnimation(SurfaceControl animationLeash, Transaction t,
399                 OnAnimationFinishedCallback finishCallback) {
400             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
401 
402             // Restore z-layering, position and stack crop until client has a chance to modify it.
403             t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex());
404             t.setPosition(animationLeash, mPosition.x, mPosition.y);
405             mTmpRect.set(mStackBounds);
406             mTmpRect.offsetTo(0, 0);
407             t.setWindowCrop(animationLeash, mTmpRect);
408             mCapturedLeash = animationLeash;
409             mCapturedFinishCallback = finishCallback;
410         }
411 
412         @Override
onAnimationCancelled(SurfaceControl animationLeash)413         public void onAnimationCancelled(SurfaceControl animationLeash) {
414             if (mRecord.mAdapter == this) {
415                 mRecord.mAdapter = null;
416             } else {
417                 mRecord.mThumbnailAdapter = null;
418             }
419             if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
420                 mPendingAnimations.remove(mRecord);
421             }
422             if (mPendingAnimations.isEmpty()) {
423                 mHandler.removeCallbacks(mTimeoutRunnable);
424                 releaseFinishedCallback();
425                 invokeAnimationCancelled();
426                 setRunningRemoteAnimation(false);
427             }
428         }
429 
430         @Override
getDurationHint()431         public long getDurationHint() {
432             return mRemoteAnimationAdapter.getDuration();
433         }
434 
435         @Override
getStatusBarTransitionsStartTime()436         public long getStatusBarTransitionsStartTime() {
437             return SystemClock.uptimeMillis()
438                     + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
439         }
440 
441         @Override
dump(PrintWriter pw, String prefix)442         public void dump(PrintWriter pw, String prefix) {
443             pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken);
444             if (mRecord.mTarget != null) {
445                 pw.print(prefix); pw.println("Target:");
446                 mRecord.mTarget.dump(pw, prefix + "  ");
447             } else {
448                 pw.print(prefix); pw.println("Target: null");
449             }
450         }
451 
452         @Override
writeToProto(ProtoOutputStream proto)453         public void writeToProto(ProtoOutputStream proto) {
454             final long token = proto.start(REMOTE);
455             if (mRecord.mTarget != null) {
456                 mRecord.mTarget.writeToProto(proto, TARGET);
457             }
458             proto.end(token);
459         }
460     }
461 }
462