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