1 /*
2  * Copyright (C) 2021 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 androidx.window.extensions.embedding;
18 
19 import static android.graphics.Matrix.MTRANS_X;
20 import static android.graphics.Matrix.MTRANS_Y;
21 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
22 
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.view.Choreographer;
26 import android.view.RemoteAnimationTarget;
27 import android.view.SurfaceControl;
28 import android.view.animation.Animation;
29 import android.view.animation.Transformation;
30 
31 import androidx.annotation.NonNull;
32 
33 /**
34  * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
35  *
36  * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
37  */
38 class TaskFragmentAnimationAdapter {
39 
40     /**
41      * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
42      */
43     private static final int LAYER_NO_OVERRIDE = -1;
44 
45     @NonNull
46     final Animation mAnimation;
47     @NonNull
48     final RemoteAnimationTarget mTarget;
49     @NonNull
50     final SurfaceControl mLeash;
51     /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
52     @NonNull
53     private final Rect mWholeAnimationBounds = new Rect();
54     /**
55      * Area in absolute coordinate that should represent all the content to show for this window.
56      * This should be the end bounds for opening window, and start bounds for closing window in case
57      * the window is resizing during the open/close transition.
58      */
59     @NonNull
60     private final Rect mContentBounds = new Rect();
61     /** Offset relative to the window parent surface for {@link #mContentBounds}. */
62     @NonNull
63     private final Point mContentRelOffset = new Point();
64 
65     @NonNull
66     final Transformation mTransformation = new Transformation();
67     @NonNull
68     final float[] mMatrix = new float[9];
69     @NonNull
70     final float[] mVecs = new float[4];
71     @NonNull
72     final Rect mRect = new Rect();
73     private boolean mIsFirstFrame = true;
74     private int mOverrideLayer = LAYER_NO_OVERRIDE;
75 
TaskFragmentAnimationAdapter(@onNull Animation animation, @NonNull RemoteAnimationTarget target)76     TaskFragmentAnimationAdapter(@NonNull Animation animation,
77             @NonNull RemoteAnimationTarget target) {
78         this(animation, target, target.leash, target.screenSpaceBounds);
79     }
80 
81     /**
82      * @param leash the surface to animate.
83      * @param wholeAnimationBounds  area in absolute coordinate that the animation surface shouldn't
84      *                              go beyond.
85      */
TaskFragmentAnimationAdapter(@onNull Animation animation, @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash, @NonNull Rect wholeAnimationBounds)86     TaskFragmentAnimationAdapter(@NonNull Animation animation,
87             @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
88             @NonNull Rect wholeAnimationBounds) {
89         mAnimation = animation;
90         mTarget = target;
91         mLeash = leash;
92         mWholeAnimationBounds.set(wholeAnimationBounds);
93         if (target.mode == MODE_CLOSING) {
94             // When it is closing, we want to show the content at the start position in case the
95             // window is resizing as well. For example, when the activities is changing from split
96             // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
97             final Rect startBounds = target.startBounds;
98             final Rect endBounds = target.screenSpaceBounds;
99             mContentBounds.set(startBounds);
100             mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
101             mContentRelOffset.offset(
102                     startBounds.left - endBounds.left,
103                     startBounds.top - endBounds.top);
104         } else {
105             mContentBounds.set(target.screenSpaceBounds);
106             mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
107         }
108     }
109 
110     /**
111      * Surface layer to be set at the first frame of the animation. We will not set the layer if it
112      * is set to {@link #LAYER_NO_OVERRIDE}.
113      */
overrideLayer(int layer)114     final void overrideLayer(int layer) {
115         mOverrideLayer = layer;
116     }
117 
118     /** Called on frame update. */
onAnimationUpdate(@onNull SurfaceControl.Transaction t, long currentPlayTime)119     final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
120         if (mIsFirstFrame) {
121             t.show(mLeash);
122             if (mOverrideLayer != LAYER_NO_OVERRIDE) {
123                 t.setLayer(mLeash, mOverrideLayer);
124             }
125             mIsFirstFrame = false;
126         }
127 
128         // Extract the transformation to the current time.
129         mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
130                 mTransformation);
131         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
132         onAnimationUpdateInner(t);
133     }
134 
135     /** To be overridden by subclasses to adjust the animation surface change. */
onAnimationUpdateInner(@onNull SurfaceControl.Transaction t)136     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
137         // Update the surface position and alpha.
138         mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
139         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
140         t.setAlpha(mLeash, mTransformation.getAlpha());
141 
142         // Get current surface bounds in absolute coordinate.
143         // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
144         final int positionX = Math.round(mMatrix[MTRANS_X]);
145         final int positionY = Math.round(mMatrix[MTRANS_Y]);
146         final Rect cropRect = new Rect(mContentBounds);
147         cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
148 
149         // Store the current offset of the surface top left from (0,0) in absolute coordinate.
150         final int offsetX = cropRect.left;
151         final int offsetY = cropRect.top;
152 
153         // Intersect to make sure the animation happens within the whole animation bounds.
154         if (!cropRect.intersect(mWholeAnimationBounds)) {
155             // Hide the surface when it is outside of the animation area.
156             t.setAlpha(mLeash, 0);
157         }
158 
159         // cropRect is in absolute coordinate, so we need to translate it to surface top left.
160         cropRect.offset(-offsetX, -offsetY);
161         t.setCrop(mLeash, cropRect);
162     }
163 
164     /** Called after animation finished. */
onAnimationEnd(@onNull SurfaceControl.Transaction t)165     final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
166         onAnimationUpdate(t, mAnimation.getDuration());
167     }
168 
getDurationHint()169     final long getDurationHint() {
170         return mAnimation.computeDurationHint();
171     }
172 
173     /**
174      * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
175      * size change.
176      */
177     static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
178 
SnapshotAdapter(@onNull Animation animation, @NonNull RemoteAnimationTarget target)179         SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
180             // Start leash is the snapshot of the starting surface.
181             super(animation, target, target.startLeash, target.screenSpaceBounds);
182         }
183 
184         @Override
onAnimationUpdateInner(@onNull SurfaceControl.Transaction t)185         void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
186             // Snapshot should always be placed at the top left of the animation leash.
187             mTransformation.getMatrix().postTranslate(0, 0);
188             t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
189             t.setAlpha(mLeash, mTransformation.getAlpha());
190         }
191     }
192 
193     /**
194      * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
195      */
196     static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
197 
BoundsChangeAdapter(@onNull Animation animation, @NonNull RemoteAnimationTarget target)198         BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
199             super(animation, target);
200         }
201 
202         @Override
onAnimationUpdateInner(@onNull SurfaceControl.Transaction t)203         void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
204             mTransformation.getMatrix().postTranslate(
205                     mTarget.localBounds.left, mTarget.localBounds.top);
206             t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
207             t.setAlpha(mLeash, mTransformation.getAlpha());
208 
209             // The following applies an inverse scale to the clip-rect so that it crops "after" the
210             // scale instead of before.
211             mVecs[1] = mVecs[2] = 0;
212             mVecs[0] = mVecs[3] = 1;
213             mTransformation.getMatrix().mapVectors(mVecs);
214             mVecs[0] = 1.f / mVecs[0];
215             mVecs[3] = 1.f / mVecs[3];
216             final Rect clipRect = mTransformation.getClipRect();
217             mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
218             mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
219             mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
220             mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
221             t.setWindowCrop(mLeash, mRect);
222         }
223     }
224 }
225