• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.quickstep.util;
18 
19 import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_PIP;
20 
21 import android.animation.Animator;
22 import android.animation.RectEvaluator;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.ActivityInfo;
26 import android.graphics.Matrix;
27 import android.graphics.Rect;
28 import android.graphics.RectF;
29 import android.util.Log;
30 import android.view.Surface;
31 import android.view.SurfaceControl;
32 import android.view.View;
33 import android.window.PictureInPictureSurfaceTransaction;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 
38 import com.android.launcher3.anim.AnimationSuccessListener;
39 import com.android.launcher3.icons.IconProvider;
40 import com.android.quickstep.TaskAnimationManager;
41 import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
42 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
43 import com.android.wm.shell.pip.PipContentOverlay;
44 
45 /**
46  * Subclass of {@link RectFSpringAnim} that animates an Activity to PiP (picture-in-picture) window
47  * when swiping up (in gesture navigation mode).
48  */
49 public class SwipePipToHomeAnimator extends RectFSpringAnim {
50     private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
51 
52     private static final float END_PROGRESS = 1.0f;
53 
54     private final int mTaskId;
55     private final ActivityInfo mActivityInfo;
56     private final SurfaceControl mLeash;
57     private final Rect mSourceRectHint = new Rect();
58     private final Rect mAppBounds = new Rect();
59     private final Matrix mHomeToWindowPositionMap = new Matrix();
60     private final Rect mStartBounds = new Rect();
61     private final RectF mCurrentBoundsF = new RectF();
62     private final Rect mCurrentBounds = new Rect();
63     private final Rect mDestinationBounds = new Rect();
64     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
65 
66     /**
67      * For calculating transform in
68      * {@link #onAnimationUpdate(SurfaceControl.Transaction, RectF, float)}
69      */
70     private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
71     private final Rect mSourceHintRectInsets;
72     private final Rect mSourceInsets = new Rect();
73 
74     /** for rotation calculations */
75     private final @RecentsOrientedState.SurfaceRotation int mFromRotation;
76     private final Rect mDestinationBoundsTransformed = new Rect();
77 
78     /**
79      * Flag to avoid the double-end problem since the leash would have been released
80      * after the first end call and any further operations upon it would lead to NPE.
81      */
82     private boolean mHasAnimationEnded;
83 
84     /**
85      * Wrapper of {@link SurfaceControl} that is used when entering PiP without valid
86      * source rect hint.
87      */
88     @Nullable
89     private PipContentOverlay mPipContentOverlay;
90 
91     /**
92      * @param context {@link Context} provides Launcher resources
93      * @param taskId Task id associated with this animator, see also {@link #getTaskId()}
94      * @param activityInfo {@link ActivityInfo} associated with this animator,
95      *                      see also {@link #getComponentName()}
96      * @param appIconSizePx The size in pixel for the app icon in content overlay
97      * @param leash {@link SurfaceControl} this animator operates on
98      * @param sourceRectHint See the definition in {@link android.app.PictureInPictureParams}
99      * @param appBounds Bounds of the application, sourceRectHint is based on this bounds
100      * @param homeToWindowPositionMap {@link Matrix} to map a Rect from home to window space
101      * @param startBounds Bounds of the application when this animator starts. This can be
102      *                    different from the appBounds if user has swiped a certain distance and
103      *                    Launcher has performed transform on the leash.
104      * @param destinationBounds Bounds of the destination this animator ends to
105      * @param fromRotation From rotation if different from final rotation, ROTATION_0 otherwise
106      * @param destinationBoundsTransformed Destination bounds in window space
107      * @param cornerRadius Corner radius in pixel value for PiP window
108      * @param shadowRadius Shadow radius in pixel value for PiP window
109      * @param view Attached view for logging purpose
110      */
SwipePipToHomeAnimator(@onNull Context context, int taskId, @NonNull ActivityInfo activityInfo, int appIconSizePx, @NonNull SurfaceControl leash, @Nullable Rect sourceRectHint, @NonNull Rect appBounds, @NonNull Matrix homeToWindowPositionMap, @NonNull RectF startBounds, @NonNull Rect destinationBounds, @RecentsOrientedState.SurfaceRotation int fromRotation, @NonNull Rect destinationBoundsTransformed, int cornerRadius, int shadowRadius, @NonNull View view)111     private SwipePipToHomeAnimator(@NonNull Context context,
112             int taskId,
113             @NonNull ActivityInfo activityInfo,
114             int appIconSizePx,
115             @NonNull SurfaceControl leash,
116             @Nullable Rect sourceRectHint,
117             @NonNull Rect appBounds,
118             @NonNull Matrix homeToWindowPositionMap,
119             @NonNull RectF startBounds,
120             @NonNull Rect destinationBounds,
121             @RecentsOrientedState.SurfaceRotation int fromRotation,
122             @NonNull Rect destinationBoundsTransformed,
123             int cornerRadius,
124             int shadowRadius,
125             @NonNull View view) {
126         super(new DefaultSpringConfig(context, null, startBounds,
127                 new RectF(destinationBoundsTransformed)));
128         mTaskId = taskId;
129         mActivityInfo = activityInfo;
130         mLeash = leash;
131         mAppBounds.set(appBounds);
132         mHomeToWindowPositionMap.set(homeToWindowPositionMap);
133         startBounds.round(mStartBounds);
134         mDestinationBounds.set(destinationBounds);
135         mFromRotation = fromRotation;
136         mDestinationBoundsTransformed.set(destinationBoundsTransformed);
137         mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
138 
139         if (sourceRectHint != null && (sourceRectHint.width() < destinationBounds.width()
140                 || sourceRectHint.height() < destinationBounds.height())) {
141             // This is a situation in which the source hint rect on at least one axis is smaller
142             // than the destination bounds, which presents a problem because we would have to scale
143             // up that axis to fit the bounds. So instead, just fallback to the non-source hint
144             // animation in this case.
145             sourceRectHint = null;
146         }
147 
148         if (sourceRectHint == null) {
149             mSourceRectHint.setEmpty();
150             mSourceHintRectInsets = null;
151 
152             // Create a new overlay layer. We do not call detach on this instance, it's propagated
153             // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
154             // the cleanup.
155             mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
156                     mAppBounds, new IconProvider(context).getIcon(mActivityInfo),
157                     appIconSizePx);
158             final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
159             mPipContentOverlay.attach(tx, mLeash);
160         } else {
161             mSourceRectHint.set(sourceRectHint);
162             mSourceHintRectInsets = new Rect(sourceRectHint.left - appBounds.left,
163                     sourceRectHint.top - appBounds.top,
164                     appBounds.right - sourceRectHint.right,
165                     appBounds.bottom - sourceRectHint.bottom);
166         }
167 
168         addAnimatorListener(new AnimationSuccessListener() {
169             @Override
170             public void onAnimationStart(Animator animation) {
171                 InteractionJankMonitorWrapper.begin(view, CUJ_APP_CLOSE_TO_PIP);
172                 super.onAnimationStart(animation);
173             }
174 
175             @Override
176             public void onAnimationCancel(Animator animation) {
177                 super.onAnimationCancel(animation);
178                 InteractionJankMonitorWrapper.cancel(CUJ_APP_CLOSE_TO_PIP);
179             }
180 
181             @Override
182             public void onAnimationSuccess(Animator animator) {
183                 InteractionJankMonitorWrapper.end(CUJ_APP_CLOSE_TO_PIP);
184             }
185 
186             @Override
187             public void onAnimationEnd(Animator animation) {
188                 if (mHasAnimationEnded) return;
189                 super.onAnimationEnd(animation);
190                 mHasAnimationEnded = true;
191             }
192         });
193         addOnUpdateListener(this::onAnimationUpdate);
194     }
195 
onAnimationUpdate(RectF currentRect, float progress)196     private void onAnimationUpdate(RectF currentRect, float progress) {
197         if (mHasAnimationEnded) return;
198         final SurfaceControl.Transaction tx =
199                 PipSurfaceTransactionHelper.newSurfaceControlTransaction();
200         mHomeToWindowPositionMap.mapRect(mCurrentBoundsF, currentRect);
201         onAnimationUpdate(tx, mCurrentBoundsF, progress);
202         tx.apply();
203     }
204 
onAnimationUpdate(SurfaceControl.Transaction tx, RectF currentRect, float progress)205     private PictureInPictureSurfaceTransaction onAnimationUpdate(SurfaceControl.Transaction tx,
206             RectF currentRect, float progress) {
207         currentRect.round(mCurrentBounds);
208         if (mPipContentOverlay != null) {
209             mPipContentOverlay.onAnimationUpdate(tx, mCurrentBounds, progress);
210         }
211         final PictureInPictureSurfaceTransaction op;
212         if (mSourceHintRectInsets == null) {
213             // no source rect hint been set, directly scale the window down
214             op = onAnimationScale(progress, tx, mCurrentBounds);
215         } else {
216             // scale and crop according to the source rect hint
217             op = onAnimationScaleAndCrop(progress, tx, mCurrentBounds);
218         }
219         return op;
220     }
221 
222     /** scale the window directly with no source rect hint being set */
onAnimationScale( float progress, SurfaceControl.Transaction tx, Rect bounds)223     private PictureInPictureSurfaceTransaction onAnimationScale(
224             float progress, SurfaceControl.Transaction tx, Rect bounds) {
225         if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
226             final RotatedPosition rotatedPosition = getRotatedPosition(progress);
227             return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds,
228                     rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
229         } else {
230             return mSurfaceTransactionHelper.scale(tx, mLeash, mAppBounds, bounds);
231         }
232     }
233 
234     /** scale and crop the window with source rect hint */
onAnimationScaleAndCrop( float progress, SurfaceControl.Transaction tx, Rect bounds)235     private PictureInPictureSurfaceTransaction onAnimationScaleAndCrop(
236             float progress, SurfaceControl.Transaction tx,
237             Rect bounds) {
238         final Rect insets = mInsetsEvaluator.evaluate(progress, mSourceInsets,
239                 mSourceHintRectInsets);
240         if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
241             final RotatedPosition rotatedPosition = getRotatedPosition(progress);
242             return mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
243                     rotatedPosition.degree, rotatedPosition.positionX, rotatedPosition.positionY);
244         } else {
245             return mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mAppBounds,
246                     bounds, insets, progress);
247         }
248     }
249 
getTaskId()250     public int getTaskId() {
251         return mTaskId;
252     }
253 
getComponentName()254     public ComponentName getComponentName() {
255         return mActivityInfo.getComponentName();
256     }
257 
getDestinationBounds()258     public Rect getDestinationBounds() {
259         return mDestinationBounds;
260     }
261 
262     @Nullable
getContentOverlay()263     public SurfaceControl getContentOverlay() {
264         return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
265     }
266 
267     /** @return {@link PictureInPictureSurfaceTransaction} for the final leash transaction. */
getFinishTransaction()268     public PictureInPictureSurfaceTransaction getFinishTransaction() {
269         // get the final leash operations but do not apply to the leash.
270         final SurfaceControl.Transaction tx =
271                 PipSurfaceTransactionHelper.newSurfaceControlTransaction();
272         final PictureInPictureSurfaceTransaction pipTx =
273                 onAnimationUpdate(tx, new RectF(mDestinationBounds), END_PROGRESS);
274         pipTx.setShouldDisableCanAffectSystemUiFlags(true);
275         return pipTx;
276     }
277 
getRotatedPosition(float progress)278     private RotatedPosition getRotatedPosition(float progress) {
279         final float degree, positionX, positionY;
280         if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
281             if (mFromRotation == Surface.ROTATION_90) {
282                 degree = -90 * (1 - progress);
283                 positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
284                         + mStartBounds.left;
285                 positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
286                         + mStartBounds.top + mStartBounds.bottom * (1 - progress);
287             } else {
288                 degree = 90 * (1 - progress);
289                 positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
290                         + mStartBounds.left + mStartBounds.right * (1 - progress);
291                 positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
292                         + mStartBounds.top;
293             }
294         } else {
295             if (mFromRotation == Surface.ROTATION_90) {
296                 degree = -90 * progress;
297                 positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
298                         + mStartBounds.left;
299                 positionY = progress * (mDestinationBoundsTransformed.bottom - mStartBounds.top)
300                         + mStartBounds.top;
301             } else {
302                 degree = 90 * progress;
303                 positionX = progress * (mDestinationBoundsTransformed.right - mStartBounds.left)
304                         + mStartBounds.left;
305                 positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
306                         + mStartBounds.top;
307             }
308         }
309 
310         return new RotatedPosition(degree, positionX, positionY);
311     }
312 
313     /** Builder class for {@link SwipePipToHomeAnimator} */
314     public static class Builder {
315         private Context mContext;
316         private int mTaskId;
317         private ActivityInfo mActivityInfo;
318         private int mAppIconSizePx;
319         private SurfaceControl mLeash;
320         private Rect mSourceRectHint;
321         private Rect mDisplayCutoutInsets;
322         private Rect mAppBounds;
323         private Matrix mHomeToWindowPositionMap;
324         private RectF mStartBounds;
325         private Rect mDestinationBounds;
326         private int mCornerRadius;
327         private int mShadowRadius;
328         private View mAttachedView;
329         private @RecentsOrientedState.SurfaceRotation int mFromRotation = Surface.ROTATION_0;
330         private final Rect mDestinationBoundsTransformed = new Rect();
331 
setContext(Context context)332         public Builder setContext(Context context) {
333             mContext = context;
334             return this;
335         }
336 
setTaskId(int taskId)337         public Builder setTaskId(int taskId) {
338             mTaskId = taskId;
339             return this;
340         }
341 
setActivityInfo(ActivityInfo activityInfo)342         public Builder setActivityInfo(ActivityInfo activityInfo) {
343             mActivityInfo = activityInfo;
344             return this;
345         }
346 
setAppIconSizePx(int appIconSizePx)347         public Builder setAppIconSizePx(int appIconSizePx) {
348             mAppIconSizePx = appIconSizePx;
349             return this;
350         }
351 
setLeash(SurfaceControl leash)352         public Builder setLeash(SurfaceControl leash) {
353             mLeash = leash;
354             return this;
355         }
356 
setSourceRectHint(Rect sourceRectHint)357         public Builder setSourceRectHint(Rect sourceRectHint) {
358             mSourceRectHint = new Rect(sourceRectHint);
359             return this;
360         }
361 
setAppBounds(Rect appBounds)362         public Builder setAppBounds(Rect appBounds) {
363             mAppBounds = new Rect(appBounds);
364             return this;
365         }
366 
setHomeToWindowPositionMap(Matrix homeToWindowPositionMap)367         public Builder setHomeToWindowPositionMap(Matrix homeToWindowPositionMap) {
368             mHomeToWindowPositionMap = new Matrix(homeToWindowPositionMap);
369             return this;
370         }
371 
setStartBounds(RectF startBounds)372         public Builder setStartBounds(RectF startBounds) {
373             mStartBounds = new RectF(startBounds);
374             return this;
375         }
376 
setDestinationBounds(Rect destinationBounds)377         public Builder setDestinationBounds(Rect destinationBounds) {
378             mDestinationBounds = new Rect(destinationBounds);
379             return this;
380         }
381 
setCornerRadius(int cornerRadius)382         public Builder setCornerRadius(int cornerRadius) {
383             mCornerRadius = cornerRadius;
384             return this;
385         }
386 
setShadowRadius(int shadowRadius)387         public Builder setShadowRadius(int shadowRadius) {
388             mShadowRadius = shadowRadius;
389             return this;
390         }
391 
setAttachedView(View attachedView)392         public Builder setAttachedView(View attachedView) {
393             mAttachedView = attachedView;
394             return this;
395         }
396 
setFromRotation(TaskViewSimulator taskViewSimulator, @RecentsOrientedState.SurfaceRotation int fromRotation, Rect displayCutoutInsets)397         public Builder setFromRotation(TaskViewSimulator taskViewSimulator,
398                 @RecentsOrientedState.SurfaceRotation int fromRotation,
399                 Rect displayCutoutInsets) {
400             if (fromRotation != Surface.ROTATION_90 && fromRotation != Surface.ROTATION_270) {
401                 Log.wtf(TAG, "Not a supported rotation, rotation=" + fromRotation);
402                 return this;
403             }
404             final Matrix matrix = new Matrix();
405             taskViewSimulator.applyWindowToHomeRotation(matrix);
406 
407             // map the destination bounds into window space. mDestinationBounds is always calculated
408             // in the final home space and the animation runs in original window space.
409             final RectF transformed = new RectF(mDestinationBounds);
410             matrix.mapRect(transformed, new RectF(mDestinationBounds));
411             transformed.round(mDestinationBoundsTransformed);
412 
413             mFromRotation = fromRotation;
414             if (displayCutoutInsets != null) {
415                 mDisplayCutoutInsets = new Rect(displayCutoutInsets);
416             }
417             return this;
418         }
419 
build()420         public SwipePipToHomeAnimator build() {
421             if (mDestinationBoundsTransformed.isEmpty()) {
422                 mDestinationBoundsTransformed.set(mDestinationBounds);
423             }
424             // adjust the mSourceRectHint / mAppBounds by display cutout if applicable.
425             if (mSourceRectHint != null && mDisplayCutoutInsets != null) {
426                 if (mFromRotation == Surface.ROTATION_90) {
427                     mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
428                 } else if (mFromRotation == Surface.ROTATION_270) {
429                     mAppBounds.inset(mDisplayCutoutInsets);
430                 }
431             }
432             return new SwipePipToHomeAnimator(mContext, mTaskId, mActivityInfo, mAppIconSizePx,
433                     mLeash, mSourceRectHint, mAppBounds,
434                     mHomeToWindowPositionMap, mStartBounds, mDestinationBounds,
435                     mFromRotation, mDestinationBoundsTransformed,
436                     mCornerRadius, mShadowRadius, mAttachedView);
437         }
438     }
439 
440     private static class RotatedPosition {
441         private final float degree;
442         private final float positionX;
443         private final float positionY;
444 
RotatedPosition(float degree, float positionX, float positionY)445         private RotatedPosition(float degree, float positionX, float positionY) {
446             this.degree = degree;
447             this.positionX = positionX;
448             this.positionY = positionY;
449         }
450     }
451 }
452