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