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