1 /*
2  * Copyright (C) 2016 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.systemui.pip.phone;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
21 
22 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
23 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
24 import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
25 
26 import android.animation.AnimationHandler;
27 import android.animation.Animator;
28 import android.animation.Animator.AnimatorListener;
29 import android.animation.AnimatorListenerAdapter;
30 import android.animation.RectEvaluator;
31 import android.animation.ValueAnimator;
32 import android.animation.ValueAnimator.AnimatorUpdateListener;
33 import android.app.ActivityManager.StackInfo;
34 import android.app.IActivityManager;
35 import android.app.IActivityTaskManager;
36 import android.content.Context;
37 import android.graphics.Point;
38 import android.graphics.PointF;
39 import android.graphics.Rect;
40 import android.os.Debug;
41 import android.os.Handler;
42 import android.os.Message;
43 import android.os.RemoteException;
44 import android.util.Log;
45 import android.view.animation.Interpolator;
46 
47 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
48 import com.android.internal.os.SomeArgs;
49 import com.android.internal.policy.PipSnapAlgorithm;
50 import com.android.systemui.shared.system.WindowManagerWrapper;
51 import com.android.systemui.statusbar.FlingAnimationUtils;
52 
53 import java.io.PrintWriter;
54 
55 /**
56  * A helper to animate and manipulate the PiP.
57  */
58 public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback {
59 
60     private static final String TAG = "PipMotionHelper";
61     private static final boolean DEBUG = false;
62 
63     private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
64 
65     private static final int DEFAULT_MOVE_STACK_DURATION = 225;
66     private static final int SNAP_STACK_DURATION = 225;
67     private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375;
68     private static final int DRAG_TO_DISMISS_STACK_DURATION = 175;
69     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
70     private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
71     private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300;
72     private static final int MINIMIZE_STACK_MAX_DURATION = 200;
73     private static final int SHIFT_DURATION = 300;
74 
75     // The fraction of the stack width that the user has to drag offscreen to minimize the PiP
76     private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f;
77     // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
78     private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f;
79 
80     private static final int MSG_RESIZE_IMMEDIATE = 1;
81     private static final int MSG_RESIZE_ANIMATE = 2;
82     private static final int MSG_OFFSET_ANIMATE = 3;
83 
84     private Context mContext;
85     private IActivityManager mActivityManager;
86     private IActivityTaskManager mActivityTaskManager;
87     private Handler mHandler;
88 
89     private PipMenuActivityController mMenuController;
90     private PipSnapAlgorithm mSnapAlgorithm;
91     private FlingAnimationUtils mFlingAnimationUtils;
92     private AnimationHandler mAnimationHandler;
93 
94     private final Rect mBounds = new Rect();
95     private final Rect mStableInsets = new Rect();
96 
97     private ValueAnimator mBoundsAnimator = null;
98 
PipMotionHelper(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils)99     public PipMotionHelper(Context context, IActivityManager activityManager,
100             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
101             PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils) {
102         mContext = context;
103         mHandler = new Handler(ForegroundThread.get().getLooper(), this);
104         mActivityManager = activityManager;
105         mActivityTaskManager = activityTaskManager;
106         mMenuController = menuController;
107         mSnapAlgorithm = snapAlgorithm;
108         mFlingAnimationUtils = flingAnimationUtils;
109         mAnimationHandler = new AnimationHandler();
110         mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider());
111         onConfigurationChanged();
112     }
113 
114     /**
115      * Updates whenever the configuration changes.
116      */
onConfigurationChanged()117     void onConfigurationChanged() {
118         mSnapAlgorithm.onConfigurationChanged();
119         WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
120     }
121 
122     /**
123      * Synchronizes the current bounds with the pinned stack.
124      */
synchronizePinnedStackBounds()125     void synchronizePinnedStackBounds() {
126         cancelAnimations();
127         try {
128             StackInfo stackInfo = mActivityTaskManager.getStackInfo(
129                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
130             if (stackInfo != null) {
131                 mBounds.set(stackInfo.bounds);
132             }
133         } catch (RemoteException e) {
134             Log.w(TAG, "Failed to get pinned stack bounds");
135         }
136     }
137 
138     /**
139      * Tries to the move the pinned stack to the given {@param bounds}.
140      */
movePip(Rect toBounds)141     void movePip(Rect toBounds) {
142         cancelAnimations();
143         resizePipUnchecked(toBounds);
144         mBounds.set(toBounds);
145     }
146 
147     /**
148      * Resizes the pinned stack back to fullscreen.
149      */
expandPip()150     void expandPip() {
151         expandPip(false /* skipAnimation */);
152     }
153 
154     /**
155      * Resizes the pinned stack back to fullscreen.
156      */
expandPip(boolean skipAnimation)157     void expandPip(boolean skipAnimation) {
158         if (DEBUG) {
159             Log.d(TAG, "expandPip: skipAnimation=" + skipAnimation
160                     + " callers=\n" + Debug.getCallers(5, "    "));
161         }
162         cancelAnimations();
163         mMenuController.hideMenuWithoutResize();
164         mHandler.post(() -> {
165             try {
166                 mActivityTaskManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
167             } catch (RemoteException e) {
168                 Log.e(TAG, "Error expanding PiP activity", e);
169             }
170         });
171     }
172 
173     /**
174      * Dismisses the pinned stack.
175      */
176     @Override
dismissPip()177     public void dismissPip() {
178         if (DEBUG) {
179             Log.d(TAG, "dismissPip: callers=\n" + Debug.getCallers(5, "    "));
180         }
181         cancelAnimations();
182         mMenuController.hideMenuWithoutResize();
183         mHandler.post(() -> {
184             try {
185                 mActivityTaskManager.removeStacksInWindowingModes(
186                         new int[]{ WINDOWING_MODE_PINNED });
187             } catch (RemoteException e) {
188                 Log.e(TAG, "Failed to remove PiP", e);
189             }
190         });
191     }
192 
193     /**
194      * @return the PiP bounds.
195      */
getBounds()196     Rect getBounds() {
197         return mBounds;
198     }
199 
200     /**
201      * @return the closest minimized PiP bounds.
202      */
getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds)203     Rect getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds) {
204         Point displaySize = new Point();
205         mContext.getDisplay().getRealSize(displaySize);
206         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, stackBounds);
207         mSnapAlgorithm.applyMinimizedOffset(toBounds, movementBounds, displaySize, mStableInsets);
208         return toBounds;
209     }
210 
211     /**
212      * @return whether the PiP at the current bounds should be minimized.
213      */
shouldMinimizePip()214     boolean shouldMinimizePip() {
215         Point displaySize = new Point();
216         mContext.getDisplay().getRealSize(displaySize);
217         if (mBounds.left < 0) {
218             float offscreenFraction = (float) -mBounds.left / mBounds.width();
219             return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
220         } else if (mBounds.right > displaySize.x) {
221             float offscreenFraction = (float) (mBounds.right - displaySize.x) /
222                     mBounds.width();
223             return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION;
224         } else {
225             return false;
226         }
227     }
228 
229     /**
230      * @return whether the PiP at the current bounds should be dismissed.
231      */
shouldDismissPip()232     boolean shouldDismissPip() {
233         Point displaySize = new Point();
234         mContext.getDisplay().getRealSize(displaySize);
235         final int y = displaySize.y - mStableInsets.bottom;
236         if (mBounds.bottom > y) {
237             float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height();
238             return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION;
239         }
240         return false;
241     }
242 
243     /**
244      * Flings the minimized PiP to the closest minimized snap target.
245      */
flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition)246     Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
247         cancelAnimations();
248         // We currently only allow flinging the minimized stack up and down, so just lock the
249         // movement bounds to the current stack bounds horizontally
250         movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
251                 movementBounds.bottom);
252         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
253                 0 /* velocityX */, velocityY, dragStartPosition);
254         if (!mBounds.equals(toBounds)) {
255             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
256             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
257                     distanceBetweenRectOffsets(mBounds, toBounds),
258                     velocityY);
259             mBoundsAnimator.start();
260         }
261         return toBounds;
262     }
263 
264     /**
265      * Animates the PiP to the minimized state, slightly offscreen.
266      */
animateToClosestMinimizedState(Rect movementBounds, AnimatorUpdateListener updateListener)267     Rect animateToClosestMinimizedState(Rect movementBounds,
268             AnimatorUpdateListener updateListener) {
269         cancelAnimations();
270         Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds);
271         if (!mBounds.equals(toBounds)) {
272             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds,
273                     MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN);
274             if (updateListener != null) {
275                 mBoundsAnimator.addUpdateListener(updateListener);
276             }
277             mBoundsAnimator.start();
278         }
279         return toBounds;
280     }
281 
282     /**
283      * Flings the PiP to the closest snap target.
284      */
flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, AnimatorUpdateListener updateListener, AnimatorListener listener, Point startPosition)285     Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
286             AnimatorUpdateListener updateListener, AnimatorListener listener,
287             Point startPosition) {
288         cancelAnimations();
289         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
290                 velocityX, velocityY, startPosition);
291         if (!mBounds.equals(toBounds)) {
292             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
293             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
294                     distanceBetweenRectOffsets(mBounds, toBounds),
295                     velocity);
296             if (updateListener != null) {
297                 mBoundsAnimator.addUpdateListener(updateListener);
298             }
299             if (listener != null){
300                 mBoundsAnimator.addListener(listener);
301             }
302             mBoundsAnimator.start();
303         }
304         return toBounds;
305     }
306 
307     /**
308      * Animates the PiP to the closest snap target.
309      */
animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener, AnimatorListener listener)310     Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener,
311             AnimatorListener listener) {
312         cancelAnimations();
313         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
314         if (!mBounds.equals(toBounds)) {
315             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
316                     FAST_OUT_SLOW_IN);
317             if (updateListener != null) {
318                 mBoundsAnimator.addUpdateListener(updateListener);
319             }
320             if (listener != null){
321                 mBoundsAnimator.addListener(listener);
322             }
323             mBoundsAnimator.start();
324         }
325         return toBounds;
326     }
327 
328     /**
329      * Animates the PiP to the expanded state to show the menu.
330      */
animateToExpandedState(Rect expandedBounds, Rect movementBounds, Rect expandedMovementBounds)331     float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
332             Rect expandedMovementBounds) {
333         float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds);
334         mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
335         resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
336         return savedSnapFraction;
337     }
338 
339     /**
340      * Animates the PiP from the expanded state to the normal state after the menu is hidden.
341      */
animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, Rect normalMovementBounds, Rect currentMovementBounds, boolean minimized, boolean immediate)342     void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
343             Rect normalMovementBounds, Rect currentMovementBounds, boolean minimized,
344             boolean immediate) {
345         if (savedSnapFraction < 0f) {
346             // If there are no saved snap fractions, then just use the current bounds
347             savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds),
348                     currentMovementBounds);
349         }
350         mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction);
351         if (minimized) {
352             normalBounds = getClosestMinimizedBounds(normalBounds, normalMovementBounds);
353         }
354         if (immediate) {
355             movePip(normalBounds);
356         } else {
357             resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
358         }
359     }
360 
361     /**
362      * Animates the PiP to offset it from the IME or shelf.
363      */
animateToOffset(Rect originalBounds, int offset)364     void animateToOffset(Rect originalBounds, int offset) {
365         cancelAnimations();
366         adjustAndAnimatePipOffset(originalBounds, offset, SHIFT_DURATION);
367     }
368 
adjustAndAnimatePipOffset(Rect originalBounds, int offset, int duration)369     private void adjustAndAnimatePipOffset(Rect originalBounds, int offset, int duration) {
370         if (offset == 0) {
371             return;
372         }
373         SomeArgs args = SomeArgs.obtain();
374         args.arg1 = originalBounds;
375         args.argi1 = offset;
376         args.argi2 = duration;
377         mHandler.sendMessage(mHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
378     }
379 
380     /**
381      * Animates the dismissal of the PiP off the edge of the screen.
382      */
animateDismiss(Rect pipBounds, float velocityX, float velocityY, AnimatorUpdateListener listener)383     Rect animateDismiss(Rect pipBounds, float velocityX, float velocityY,
384             AnimatorUpdateListener listener) {
385         cancelAnimations();
386         final float velocity = PointF.length(velocityX, velocityY);
387         final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
388         Point p = getDismissEndPoint(pipBounds, velocityX, velocityY, isFling);
389         Rect toBounds = new Rect(pipBounds);
390         toBounds.offsetTo(p.x, p.y);
391         mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION,
392                 FAST_OUT_LINEAR_IN);
393         mBoundsAnimator.addListener(new AnimatorListenerAdapter() {
394             @Override
395             public void onAnimationEnd(Animator animation) {
396                 dismissPip();
397             }
398         });
399         if (isFling) {
400             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
401                     distanceBetweenRectOffsets(mBounds, toBounds), velocity);
402         }
403         if (listener != null) {
404             mBoundsAnimator.addUpdateListener(listener);
405         }
406         mBoundsAnimator.start();
407         return toBounds;
408     }
409 
410     /**
411      * Cancels all existing animations.
412      */
cancelAnimations()413     void cancelAnimations() {
414         if (mBoundsAnimator != null) {
415             mBoundsAnimator.cancel();
416             mBoundsAnimator = null;
417         }
418     }
419 
420     /**
421      * Creates an animation to move the PiP to give given {@param toBounds}.
422      */
createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration, Interpolator interpolator)423     private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration,
424             Interpolator interpolator) {
425         ValueAnimator anim = new ValueAnimator() {
426             @Override
427             public AnimationHandler getAnimationHandler() {
428                 return mAnimationHandler;
429             }
430         };
431         anim.setObjectValues(fromBounds, toBounds);
432         anim.setEvaluator(RECT_EVALUATOR);
433         anim.setDuration(duration);
434         anim.setInterpolator(interpolator);
435         anim.addUpdateListener((ValueAnimator animation) -> {
436             resizePipUnchecked((Rect) animation.getAnimatedValue());
437         });
438         return anim;
439     }
440 
441     /**
442      * Directly resizes the PiP to the given {@param bounds}.
443      */
resizePipUnchecked(Rect toBounds)444     private void resizePipUnchecked(Rect toBounds) {
445         if (DEBUG) {
446             Log.d(TAG, "resizePipUnchecked: toBounds=" + toBounds
447                     + " callers=\n" + Debug.getCallers(5, "    "));
448         }
449         if (!toBounds.equals(mBounds)) {
450             SomeArgs args = SomeArgs.obtain();
451             args.arg1 = toBounds;
452             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args));
453         }
454     }
455 
456     /**
457      * Directly resizes the PiP to the given {@param bounds}.
458      */
resizeAndAnimatePipUnchecked(Rect toBounds, int duration)459     private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
460         if (DEBUG) {
461             Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds
462                     + " duration=" + duration + " callers=\n" + Debug.getCallers(5, "    "));
463         }
464         if (!toBounds.equals(mBounds)) {
465             SomeArgs args = SomeArgs.obtain();
466             args.arg1 = toBounds;
467             args.argi1 = duration;
468             mHandler.sendMessage(mHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
469         }
470     }
471 
472     /**
473      * @return the coordinates the PIP should animate to based on the direction of velocity when
474      *         dismissing.
475      */
getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling)476     private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) {
477         Point displaySize = new Point();
478         mContext.getDisplay().getRealSize(displaySize);
479         final float bottomBound = displaySize.y + pipBounds.height() * .1f;
480         if (isFling && velX != 0 && velY != 0) {
481             // Line is defined by: y = mx + b, m = slope, b = y-intercept
482             // Find the slope
483             final float slope = velY / velX;
484             // Sub in slope and PiP position to solve for y-intercept: b = y - mx
485             final float yIntercept = pipBounds.top - slope * pipBounds.left;
486             // Now find the point on this line when y = bottom bound: x = (y - b) / m
487             final float x = (bottomBound - yIntercept) / slope;
488             return new Point((int) x, (int) bottomBound);
489         } else {
490             // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement,
491             // just animate downwards.
492             return new Point(pipBounds.left, (int) bottomBound);
493         }
494     }
495 
496     /**
497      * @return whether the gesture it towards the dismiss area based on the velocity when
498      *         dismissing.
499      */
isGestureToDismissArea(Rect pipBounds, float velX, float velY, boolean isFling)500     public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY,
501             boolean isFling) {
502         Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling);
503         // Center the point
504         endpoint.x += pipBounds.width() / 2;
505         endpoint.y += pipBounds.height() / 2;
506 
507         // The dismiss area is the middle third of the screen, half the PIP's height from the bottom
508         Point size = new Point();
509         mContext.getDisplay().getRealSize(size);
510         final int left = size.x / 3;
511         Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2,
512                 size.y + pipBounds.height());
513         return dismissArea.contains(endpoint.x, endpoint.y);
514     }
515 
516     /**
517      * @return the distance between points {@param p1} and {@param p2}.
518      */
distanceBetweenRectOffsets(Rect r1, Rect r2)519     private float distanceBetweenRectOffsets(Rect r1, Rect r2) {
520         return PointF.length(r1.left - r2.left, r1.top - r2.top);
521     }
522 
523     /**
524      * Handles messages to be processed on the background thread.
525      */
handleMessage(Message msg)526     public boolean handleMessage(Message msg) {
527         switch (msg.what) {
528             case MSG_RESIZE_IMMEDIATE: {
529                 SomeArgs args = (SomeArgs) msg.obj;
530                 Rect toBounds = (Rect) args.arg1;
531                 try {
532                     mActivityTaskManager.resizePinnedStack(
533                             toBounds, null /* tempPinnedTaskBounds */);
534                     mBounds.set(toBounds);
535                 } catch (RemoteException e) {
536                     Log.e(TAG, "Could not resize pinned stack to bounds: " + toBounds, e);
537                 }
538                 return true;
539             }
540 
541             case MSG_RESIZE_ANIMATE: {
542                 SomeArgs args = (SomeArgs) msg.obj;
543                 Rect toBounds = (Rect) args.arg1;
544                 int duration = args.argi1;
545                 try {
546                     StackInfo stackInfo = mActivityTaskManager.getStackInfo(
547                             WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
548                     if (stackInfo == null) {
549                         // In the case where we've already re-expanded or dismissed the PiP, then
550                         // just skip the resize
551                         return true;
552                     }
553 
554                     mActivityTaskManager.resizeStack(stackInfo.stackId, toBounds,
555                             false /* allowResizeInDockedMode */, true /* preserveWindows */,
556                             true /* animate */, duration);
557                     mBounds.set(toBounds);
558                 } catch (RemoteException e) {
559                     Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e);
560                 }
561                 return true;
562             }
563 
564             case MSG_OFFSET_ANIMATE: {
565                 SomeArgs args = (SomeArgs) msg.obj;
566                 Rect originalBounds = (Rect) args.arg1;
567                 final int offset = args.argi1;
568                 final int duration = args.argi2;
569                 try {
570                     StackInfo stackInfo = mActivityTaskManager.getStackInfo(
571                             WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
572                     if (stackInfo == null) {
573                         // In the case where we've already re-expanded or dismissed the PiP, then
574                         // just skip the resize
575                         return true;
576                     }
577 
578                     mActivityTaskManager.offsetPinnedStackBounds(stackInfo.stackId, originalBounds,
579                             0/* xOffset */, offset, duration);
580                     Rect toBounds = new Rect(originalBounds);
581                     toBounds.offset(0, offset);
582                     mBounds.set(toBounds);
583                 } catch (RemoteException e) {
584                     Log.e(TAG, "Could not animate offset pinned stack with offset: " + offset, e);
585                 }
586                 return true;
587             }
588 
589             default:
590                 return false;
591         }
592     }
593 
dump(PrintWriter pw, String prefix)594     public void dump(PrintWriter pw, String prefix) {
595         final String innerPrefix = prefix + "  ";
596         pw.println(prefix + TAG);
597         pw.println(innerPrefix + "mBounds=" + mBounds);
598         pw.println(innerPrefix + "mStableInsets=" + mStableInsets);
599     }
600 }
601