1 /*
2  * Copyright (C) 2023 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.car.portraitlauncher.panel;
18 
19 import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE;
20 import static android.provider.Settings.Global.getFloat;
21 
22 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_GRIP_BAR_CLICKED;
23 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_GRIP_BAR_DRAG;
24 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_PANEL_READY;
25 import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.createReason;
26 
27 import android.annotation.SuppressLint;
28 import android.app.TaskInfo;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.res.Resources;
32 import android.graphics.Insets;
33 import android.graphics.Point;
34 import android.graphics.Rect;
35 import android.os.Build;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.view.SurfaceView;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.FrameLayout;
42 import android.widget.RelativeLayout;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 
47 import com.android.car.portraitlauncher.R;
48 import com.android.car.portraitlauncher.panel.animation.ClosePanelAnimator;
49 import com.android.car.portraitlauncher.panel.animation.ExpandPanelAnimator;
50 import com.android.car.portraitlauncher.panel.animation.FadeInPanelAnimator;
51 import com.android.car.portraitlauncher.panel.animation.FadeOutPanelAnimator;
52 import com.android.car.portraitlauncher.panel.animation.FullScreenPanelAnimator;
53 import com.android.car.portraitlauncher.panel.animation.OpenPanelAnimator;
54 import com.android.car.portraitlauncher.panel.animation.OpenPanelWithIconAnimator;
55 import com.android.car.portraitlauncher.panel.animation.PanelAnimator;
56 
57 /**
58  * A view container used to display CarTaskViews.
59  *
60  * This panel can transition between various states, e.g. open, close and full screen.
61  * When panel is in open state it shows a grab bar to the users which can be dragged to transition
62  * to the other states.
63  */
64 public class TaskViewPanel extends RelativeLayout {
65 
66     private static final String TAG = TaskViewPanel.class.getSimpleName();
67     private static final boolean DBG = Build.IS_DEBUGGABLE;
68 
69     /** The properties of each valid state of the panel. */
70     public static class State {
71         /** The bounds used for the panel when put in this state. */
72         Rect mBounds = new Rect();
73         /** The insets used for the panel. */
74         Insets mInsets = Insets.NONE;
75         /** Whether or not the panel should display the grip bar. */
76         private final boolean mHasGripBar;
77         /** Whether the panel is visible when put in this state. */
78         private final boolean mIsVisible;
79         /** Whether the panel is considered full screen when put in this state. */
80         private final boolean mIsFullScreen;
81         /** Whether the panel should display the toolbar. */
82         private boolean mHasToolBar;
83 
State(boolean hasGripBar, boolean isVisible, boolean isFullScreen, boolean hasToolBar)84         public State(boolean hasGripBar, boolean isVisible, boolean isFullScreen,
85                 boolean hasToolBar) {
86             mHasGripBar = hasGripBar;
87             mIsVisible = isVisible;
88             mIsFullScreen = isFullScreen;
89             mHasToolBar = hasToolBar;
90         }
91 
hasGripBar()92         boolean hasGripBar() {
93             return mHasGripBar;
94         }
95 
hasToolBar()96         boolean hasToolBar() {
97             return mHasToolBar;
98         }
99 
100         /** Whether the panel in this state has any visible parts. */
isVisible()101         public boolean isVisible() {
102             return mIsVisible;
103         }
104 
105         /** Is this state considered full screen or not. */
isFullScreen()106         public boolean isFullScreen() {
107             return mIsFullScreen;
108         }
109 
110         /** The string representation of the state. Used for debugging */
toString()111         public String toString() {
112             return "(visible: " + isVisible() + ", fullscreen: " + isFullScreen() + ", bounds: "
113                     + mBounds + ")";
114         }
115     }
116 
117     /** Notifies the listener when the panel state changes. */
118     public interface OnStateChangeListener {
119         /**
120          * Called right before the panel state changes.
121          *
122          * @param oldState The state from which the transition is about to start.
123          * @param newState The final state of the panel after the transition.
124          * @param animated If the transition is animated.
125          * @param reason   The reason for the state change.
126          */
onStateChangeStart(State oldState, State newState, boolean animated, TaskViewPanelStateChangeReason reason)127         void onStateChangeStart(State oldState, State newState, boolean animated,
128                 TaskViewPanelStateChangeReason reason);
129 
130         /**
131          * Called right after the panel state changes.
132          *
133          * If the transition is animated, this method would be called after the animation.
134          *
135          * @param oldState The state from which the transition started.
136          * @param newState The final state of the panel after the transition.
137          * @param animated If the transition is animated.
138          * @param reason   The reason for the state change.
139          */
onStateChangeEnd(State oldState, State newState, boolean animated, TaskViewPanelStateChangeReason reason)140         void onStateChangeEnd(State oldState, State newState, boolean animated,
141                 TaskViewPanelStateChangeReason reason);
142     }
143 
logIfDebuggable(String message)144     private static void logIfDebuggable(String message) {
145         if (DBG) {
146             Log.d(TAG, message);
147         }
148     }
149 
150     /** The properties of the panel when in {@code open} state. */
151     private final State mOpenState;
152     /** The properties of the panel when in {@code close} state. */
153     private final State mCloseState;
154     /** The properties of the panel when in {@code full screen} state. */
155     private final State mFullScreenState;
156 
157     /**
158      * The current state of the panel.
159      *
160      * When transitioning from an state to another, this value will show the final state of the
161      * animation.
162      */
163     private State mActiveState;
164 
165     /**
166      * The current animator if there is an on-going animation.
167      */
168     private PanelAnimator mActiveAnimator;
169 
170     /** An optional listener to observe when the panel state changes. */
171     private OnStateChangeListener mOnStateChangeListener;
172 
173     /** The drag threshold after which the panel transitions to the close mode. */
174     private final int mDragThreshold;
175 
176     /** The top margin for task view panel. */
177     private final int mPanelTopMargin;
178 
179     /** The grip bar used to drag the panel. */
180     private GripBarView mGripBar;
181 
182     /** The toolbar on top of the panel. */
183     private ToolBarView mToolBarView;
184 
185     /** Internal container of the {@code CarTaskView}. */
186     private ViewGroup mTaskViewContainer;
187 
188     /** A view that is shown on top of the task view and used to improve visual effects. */
189     private TaskViewPanelOverlay mTaskViewOverlay;
190 
191     /**
192      * The {@code View} embedded in this panel, used to show application. This is the main
193      * content of the panel, should be instance of CarTaskView or RemoteCarTaskView.
194      */
195     private View mTaskView;
196 
197     /** The last reported window bounds of the task view. */
198     private Rect mTaskViewWindowBounds;
199 
200     /** The surface view showed on the back of the panel. */
201     private BackgroundSurfaceView mBackgroundSurfaceView;
202 
203     /** The flag indicating if the task view on the panel is ready. */
204     private boolean mIsReady;
205 
206     /** The current task running inside panel. */
207     private TaskInfo mCurrentTask;
208 
TaskViewPanel(Context context)209     public TaskViewPanel(Context context) {
210         this(context, null);
211     }
212 
TaskViewPanel(Context context, @Nullable AttributeSet attrs)213     public TaskViewPanel(Context context,
214             @Nullable AttributeSet attrs) {
215         this(context, attrs, 0);
216     }
217 
TaskViewPanel(Context context, @Nullable AttributeSet attrs, int defStyleAttr)218     public TaskViewPanel(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
219         this(context, attrs, defStyleAttr, 0);
220     }
221 
TaskViewPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)222     public TaskViewPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
223         super(context, attrs, defStyleAttr, defStyleRes);
224 
225         mDragThreshold = (int) getResources().getDimension(R.dimen.panel_drag_threshold);
226         mPanelTopMargin = (int) getResources().getDimension(R.dimen.panel_default_top_margin);
227 
228         mOpenState = new State(/* hasGripBar = */ true, /* isVisible = */ true,
229                 /* isFullScreen */false, /* hasToolBar = */ false);
230         mCloseState = new State(/* hasGripBar = */ true, /* isVisible = */ false,
231                 /* isFullScreen */false, /* hasToolBar = */ false);
232         mFullScreenState = new State(/* hasGripBar = */ false, /* isVisible = */ true,
233                 /* isFullScreen */true, /* hasToolBar = */ true);
234 
235         mCurrentTask = null;
236     }
237 
238     @Override
onFinishInflate()239     protected void onFinishInflate() {
240         super.onFinishInflate();
241 
242         mGripBar = findViewById(R.id.grip_bar);
243         mToolBarView = findViewById(R.id.toolbar);
244         mTaskViewContainer = findViewById(R.id.task_view_container);
245         mTaskViewOverlay = findViewById(R.id.task_view_overlay);
246         mBackgroundSurfaceView = findViewById(R.id.surface_view);
247         mActiveState = mCloseState;
248 
249         setupGripBar();
250     }
251 
252     /** Whether the panel is in the open state. */
isOpen()253     public boolean isOpen() {
254         return mActiveState == mOpenState;
255     }
256 
257     /** Whether the panel is in the full screen state. */
isFullScreen()258     public boolean isFullScreen() {
259         return mActiveState.isFullScreen();
260     }
261 
262     /** Whether the panel is visible */
isVisible()263     public boolean isVisible() {
264         return mActiveState.isVisible();
265     }
266 
267     /** Whether the panel is actively animating. */
isAnimating()268     public boolean isAnimating() {
269         return mActiveAnimator != null;
270     }
271 
272     /** Transitions the panel into the open state. */
openPanel(TaskViewPanelStateChangeReason reason)273     public void openPanel(TaskViewPanelStateChangeReason reason) {
274         openPanel(/* animated= */ true, reason);
275     }
276 
277     /** Transitions the panel into the open state. */
openPanel(boolean animated, TaskViewPanelStateChangeReason reason)278     public void openPanel(boolean animated, TaskViewPanelStateChangeReason reason) {
279         PanelAnimator animator =
280                 animated ? new OpenPanelAnimator(this, mOpenState.mBounds,
281                         getAnimationScale(getContext())) : null;
282         setActiveState(mOpenState, animator, reason);
283     }
284 
285     /** Transitions the panel into the open state with overlay and centered icon. */
openPanelWithIcon(TaskViewPanelStateChangeReason reason)286     public void openPanelWithIcon(TaskViewPanelStateChangeReason reason) {
287         PanelAnimator animator = new OpenPanelWithIconAnimator(this, mOpenState.mBounds,
288                 mTaskViewOverlay, getAnimationScale(getContext()));
289         setActiveState(mOpenState, animator, reason);
290     }
291 
292     /** Transitions the panel into the close state. */
closePanel(TaskViewPanelStateChangeReason reason)293     public void closePanel(TaskViewPanelStateChangeReason reason) {
294         closePanel(/* animated= */ true, reason);
295     }
296 
297     /** Transitions the panel into the close state. */
closePanel(boolean animated, TaskViewPanelStateChangeReason reason)298     public void closePanel(boolean animated, TaskViewPanelStateChangeReason reason) {
299         PanelAnimator animator =
300                 animated ? new ClosePanelAnimator(this, mCloseState.mBounds,
301                         getAnimationScale(getContext())) : null;
302 
303         setActiveState(mCloseState, animator, reason);
304     }
305 
306     /** Transitions the panel into the open state using the expand animation. */
expandPanel(TaskViewPanelStateChangeReason reason)307     public void expandPanel(TaskViewPanelStateChangeReason reason) {
308         Point origin = new Point(mOpenState.mBounds.centerX(), mOpenState.mBounds.centerY());
309         PanelAnimator animator =
310                 new ExpandPanelAnimator(/* panel= */ this, origin, mOpenState.mBounds, mGripBar,
311                         getAnimationScale(getContext()));
312         setActiveState(mOpenState, animator, reason);
313     }
314 
315     /** Transitions the panel into the open state using the fade-in animation. */
fadeInPanel(TaskViewPanelStateChangeReason reason)316     public void fadeInPanel(TaskViewPanelStateChangeReason reason) {
317         setActiveState(mOpenState,
318                 new FadeInPanelAnimator(/* panel= */ this, mTaskView, mOpenState.mBounds,
319                         getAnimationScale(getContext())), reason);
320     }
321 
322     /** Transitions the panel into the close state using the fade-out animation. */
fadeOutPanel(TaskViewPanelStateChangeReason reason)323     public void fadeOutPanel(TaskViewPanelStateChangeReason reason) {
324         PanelAnimator animator = new FadeOutPanelAnimator(/* panel= */ this, mTaskViewOverlay,
325                 mTaskView, mCloseState.mBounds, mCloseState.mBounds.top,
326                 getAnimationScale(getContext()));
327         setActiveState(mCloseState, animator, reason);
328     }
329 
330     /**
331      * Transitions the panel into the full screen state. During
332      * transition,{@link mTaskViewOverlay} shows with given {@code drawable} at the center.
333      */
openFullScreenPanel(boolean animated, boolean showToolBar, int bottomAdjustment, TaskViewPanelStateChangeReason reason)334     public void openFullScreenPanel(boolean animated, boolean showToolBar, int bottomAdjustment,
335             TaskViewPanelStateChangeReason reason) {
336         mFullScreenState.mHasToolBar = showToolBar;
337         mFullScreenState.mBounds.bottom = ((ViewGroup) getParent()).getHeight() - bottomAdjustment;
338         setActiveState(mFullScreenState, animated ? createFullScreenPanelAnimator() : null, reason);
339     }
340 
341     /** Sets the state change listener for the panel. */
setOnStateChangeListener(OnStateChangeListener listener)342     public void setOnStateChangeListener(OnStateChangeListener listener) {
343         mOnStateChangeListener = listener;
344     }
345 
346     /** Sets the component that {@link mTaskViewOverlay} covers */
setComponentName(@onNull ComponentName componentName)347     public void setComponentName(@NonNull ComponentName componentName) {
348         mTaskViewOverlay.setComponentName(componentName);
349         mGripBar.update(componentName);
350     }
351 
352     /**
353      * Returns the grip bar bounds of the current state.
354      *
355      * Note that the visual grip bar bounds might be different from the value here during
356      * transition animations.
357      *
358      * @param bounds The {@code Rect} that is used to return the data.
359      */
getGripBarBounds(Rect bounds)360     public void getGripBarBounds(Rect bounds) {
361         if (mActiveState.hasGripBar()) {
362             bounds.set(mActiveState.mBounds);
363             bounds.bottom = mActiveState.mBounds.top + mGripBar.getHeight();
364         } else {
365             bounds.setEmpty();
366         }
367     }
368 
369     /** Updates the {@code TaskView} used in the panel. */
setTaskView(SurfaceView surfaceView)370     public void setTaskView(SurfaceView surfaceView) {
371         mTaskView = surfaceView;
372         mTaskViewContainer.addView(mTaskView);
373         onParentDimensionChanged();
374     }
375 
376     /** Updates the readiness state of the panel. */
setReady(boolean isReady)377     public void setReady(boolean isReady) {
378         mIsReady = isReady;
379         if (mIsReady) {
380             closePanel(createReason(ON_PANEL_READY));
381         }
382     }
383 
384     /** Returns whether the panel is ready. */
isReady()385     public boolean isReady() {
386         return mIsReady;
387     }
388 
389     /** Refreshes the panel according to the given {@code Theme}. */
refresh(Resources.Theme theme)390     public void refresh(Resources.Theme theme) {
391         int backgroundColor = getResources().getColor(R.color.car_background, theme);
392         mTaskViewContainer.setBackgroundColor(backgroundColor);
393         mTaskViewOverlay.refresh();
394         mGripBar.refresh(theme);
395         mBackgroundSurfaceView.refresh(theme);
396     }
397 
398     @SuppressLint("ClickableViewAccessibility")
setupGripBar()399     private void setupGripBar() {
400         mGripBar.setOnTouchListener(new OnPanelDragListener(getContext()) {
401             @Override
402             void onClick() {
403                 closePanel(createReason(ON_GRIP_BAR_CLICKED));
404             }
405 
406             @Override
407             public void onDragBegin() {
408             }
409 
410             @Override
411             public void onDrag(int deltaX, int deltaY) {
412                 deltaY = Math.max(0, deltaY);
413                 Rect rect = new Rect(mActiveState.mBounds);
414                 rect.offset(/* dx= */ 0, deltaY);
415                 updateBounds(rect);
416             }
417 
418             @Override
419             public void onDragEnd(int deltaX, int deltaY) {
420                 deltaY = Math.max(0, deltaY);
421                 if (deltaY > mDragThreshold) {
422                     closePanel(createReason(ON_GRIP_BAR_DRAG));
423                 } else {
424                     openPanel(createReason(ON_GRIP_BAR_DRAG));
425                 }
426             }
427         });
428     }
429 
430     /** Returns the bounds of the task view once fully transitioned to the active state */
getTaskViewBounds(State state)431     public Rect getTaskViewBounds(State state) {
432         Rect bounds = new Rect(state.mBounds);
433         bounds.inset(mActiveState.mInsets);
434 
435         if (state.hasGripBar()) {
436             bounds.top += mGripBar.getHeight();
437         }
438         if (state.hasToolBar()) {
439             bounds.top += mToolBarView.getHeight();
440         }
441 
442         return bounds;
443     }
444 
445     /** Updates the insets of the taskView for non-fullscreen states. */
setInsets(Insets insets)446     public void setInsets(Insets insets) {
447         if (insets.equals(mOpenState.mInsets)) {
448             return;
449         }
450         mOpenState.mInsets = insets;
451         mCloseState.mInsets = insets;
452         if (!isReady()) {
453             return;
454         }
455         updateInsets(mActiveState.mInsets);
456         recalculateBounds();
457         updateBounds(mActiveState.mBounds);
458     }
459 
460     /** Sets a fixed background color for the task view. */
setTaskViewBackgroundColor(int color)461     public void setTaskViewBackgroundColor(int color) {
462         mBackgroundSurfaceView.setFixedColor(color);
463     }
464 
465     /** Should be called when the view is no longer in use. */
onDestroy()466     public void onDestroy() {
467         mTaskView = null;
468         mCurrentTask = null;
469     }
470 
471     /** Should be called when the parent dimension changes. */
onParentDimensionChanged()472     public void onParentDimensionChanged() {
473         int parentWidth = ((ViewGroup) getParent()).getWidth();
474         int parentHeight = ((ViewGroup) getParent()).getHeight();
475 
476         logIfDebuggable("onDimensionChanged: " + parentWidth + " " + parentHeight);
477 
478         recalculateBounds();
479         updateBounds(mActiveState.mBounds);
480     }
481 
482     /**
483      * Set Callback for {@link ToolBarView} on {@link TaskViewPanel}
484      */
setToolBarCallback(ToolBarView.Callback callback)485     public void setToolBarCallback(ToolBarView.Callback callback) {
486         mToolBarView.registerToolbarCallback(callback);
487     }
488 
489     /**
490      * Show/hide the content in {@link ToolBarView}
491      */
setToolBarViewVisibility(boolean isVisible)492     public void setToolBarViewVisibility(boolean isVisible) {
493         mToolBarView.updateToolBarContentVisibility(mActiveState.hasToolBar() && isVisible);
494     }
495 
recalculateBounds()496     private void recalculateBounds() {
497         int parentWidth = ((ViewGroup) getParent()).getWidth();
498         int parentHeight = ((ViewGroup) getParent()).getHeight();
499 
500         mOpenState.mBounds.set(0, mPanelTopMargin + mGripBar.getHeight(), parentWidth,
501                 parentHeight);
502         mCloseState.mBounds.set(0, parentHeight, parentWidth,
503                 parentHeight + mOpenState.mBounds.height());
504         mFullScreenState.mBounds.set(0, 0, parentWidth, parentHeight);
505     }
506 
setActiveState(State toState, PanelAnimator animator, TaskViewPanelStateChangeReason reason)507     private void setActiveState(State toState, PanelAnimator animator,
508             TaskViewPanelStateChangeReason reason) {
509         if (!isReady()) {
510             logIfDebuggable("Skipping state change. Not Ready.");
511         }
512 
513         if (toState.mBounds.height() == 0) {
514             logIfDebuggable("Skipping state change. Not initialized.");
515             return;
516         }
517 
518         State fromState = mActiveState;
519         logIfDebuggable("Panel( " + getTag() + ") active state changes from " + fromState
520                 + " to " + toState + " with reason code = " + reason);
521 
522         if (mActiveAnimator != null) {
523             // Should try to avoid cancelling panel animation, might cause flicker.
524             Log.e(TAG, "Cancelling the old panel animation");
525             mActiveAnimator.cancel();
526             mActiveAnimator = null;
527             mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
528             mToolBarView.setVisibility(GONE);
529         }
530 
531         float animationScale = getAnimationScale(getContext());
532         logIfDebuggable("animationScale = " + animationScale);
533         boolean animated = animator != null && animationScale != 0f;
534         onStateChangeStart(fromState, toState, animated, reason);
535 
536         mActiveState = toState;
537         mActiveAnimator = animator;
538 
539         updateInsets(mActiveState.mInsets);
540 
541         if (animated) {
542             // Change toolbar and grip bar visibilities before the animation for better animation.
543             if (toState.hasToolBar() != fromState.hasToolBar()) {
544                 mToolBarView.setVisibility(toState.hasToolBar() ? VISIBLE : GONE);
545             }
546 
547             if (toState.hasGripBar() != fromState.hasGripBar()) {
548                 mGripBar.setVisibility(toState.hasGripBar() ? VISIBLE : GONE);
549             }
550 
551             post(() -> animator.animate(() -> {
552                 mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
553                 mToolBarView.setVisibility(mActiveState.hasToolBar() ? VISIBLE : GONE);
554                 updateBounds(mActiveState.mBounds);
555                 onStateChangeEnd(fromState, mActiveState, /* animated= */ true, reason);
556             }));
557         } else {
558             mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
559             mToolBarView.setVisibility(mActiveState.hasToolBar() ? VISIBLE : GONE);
560             updateBounds(mActiveState.mBounds);
561             mTaskViewOverlay.setVisibility(GONE);
562             onStateChangeEnd(fromState, mActiveState, /* animated= */ false, reason);
563         }
564     }
565 
onStateChangeStart(State fromState, State toState, boolean animated, TaskViewPanelStateChangeReason reason)566     private void onStateChangeStart(State fromState, State toState, boolean animated,
567             TaskViewPanelStateChangeReason reason) {
568         if (mOnStateChangeListener != null) {
569             mOnStateChangeListener.onStateChangeStart(fromState, toState, animated, reason);
570         }
571     }
572 
onStateChangeEnd(State fromState, State toState, boolean animated, TaskViewPanelStateChangeReason reason)573     private void onStateChangeEnd(State fromState, State toState, boolean animated,
574             TaskViewPanelStateChangeReason reason) {
575         mActiveAnimator = null;
576         if (mOnStateChangeListener != null) {
577             mOnStateChangeListener.onStateChangeEnd(fromState, toState, animated, reason);
578         }
579     }
580 
581     /** Updates bounds for {@link mTaskView}. */
updateTaskViewBounds(Rect bounds)582     public void updateTaskViewBounds(Rect bounds) {
583         mTaskViewWindowBounds = bounds;
584     }
585 
updateInsets(Insets insets)586     private void updateInsets(Insets insets) {
587         mTaskViewContainer.setPadding(insets.left, insets.top, insets.right, insets.bottom);
588     }
589 
updateBounds(Rect bounds)590     private void updateBounds(Rect bounds) {
591         final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
592         layoutParams.topMargin = bounds.top;
593         layoutParams.rightMargin = bounds.right;
594         layoutParams.width = bounds.width();
595         layoutParams.height = bounds.height();
596         setLayoutParams(layoutParams);
597     }
598 
createFullScreenPanelAnimator()599     private FullScreenPanelAnimator createFullScreenPanelAnimator() {
600         Point offset = new Point(mOpenState.mBounds.left, mOpenState.mBounds.top);
601         Rect bounds = mFullScreenState.mBounds;
602         return new FullScreenPanelAnimator(this, bounds, offset, mTaskViewOverlay,
603                 getAnimationScale(getContext()));
604     }
605 
getAnimationScale(Context context)606     private static float getAnimationScale(Context context) {
607         return getFloat(context.getContentResolver(), ANIMATOR_DURATION_SCALE, /* def= */ 1.0f);
608     }
609 }
610