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