Home | History | Annotate | Download | only in wm
      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 
     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      */
     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      */
     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 
    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 
    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 
    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 
    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 
    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 
    216     private void releaseFinishedCallback() {
    217         if (mFinishedCallback != null) {
    218             mFinishedCallback.release();
    219             mFinishedCallback = null;
    220         }
    221     }
    222 
    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 
    231     private void linkToDeathOfRunner() throws RemoteException {
    232         if (!mLinkedToDeathOfRunner) {
    233             mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0);
    234             mLinkedToDeathOfRunner = true;
    235         }
    236     }
    237 
    238     private void unlinkToDeathOfRunner() {
    239         if (mLinkedToDeathOfRunner) {
    240             mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0);
    241             mLinkedToDeathOfRunner = false;
    242         }
    243     }
    244 
    245     @Override
    246     public void binderDied() {
    247         cancelAnimation("binderDied");
    248     }
    249 
    250     private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
    251 
    252         RemoteAnimationController mOuter;
    253 
    254         FinishedCallback(RemoteAnimationController outer) {
    255             mOuter = outer;
    256         }
    257 
    258         @Override
    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          */
    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 
    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 
    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 
    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
    327         public boolean getDetachWallpaper() {
    328             return false;
    329         }
    330 
    331         @Override
    332         public boolean getShowWallpaper() {
    333             return false;
    334         }
    335 
    336         @Override
    337         public int getBackgroundColor() {
    338             return 0;
    339         }
    340 
    341         @Override
    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
    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
    368         public long getDurationHint() {
    369             return mRemoteAnimationAdapter.getDuration();
    370         }
    371 
    372         @Override
    373         public long getStatusBarTransitionsStartTime() {
    374             return SystemClock.uptimeMillis()
    375                     + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
    376         }
    377 
    378         @Override
    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
    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