• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep.views;
17 
18 import android.animation.Animator;
19 import android.animation.Animator.AnimatorListener;
20 import android.annotation.TargetApi;
21 import android.content.Context;
22 import android.graphics.Matrix;
23 import android.graphics.RectF;
24 import android.os.Build;
25 import android.util.AttributeSet;
26 import android.util.Size;
27 import android.view.GhostView;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
31 import android.widget.FrameLayout;
32 
33 import com.android.launcher3.Launcher;
34 import com.android.launcher3.R;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.dragndrop.DragLayer;
37 import com.android.launcher3.util.Themes;
38 import com.android.launcher3.views.FloatingView;
39 import com.android.launcher3.views.ListenerView;
40 import com.android.launcher3.widget.LauncherAppWidgetHostView;
41 import com.android.launcher3.widget.RoundedCornerEnforcement;
42 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
43 
44 /** A view that mimics an App Widget through a launch animation. */
45 @TargetApi(Build.VERSION_CODES.S)
46 public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
47         OnGlobalLayoutListener, FloatingView {
48     private static final Matrix sTmpMatrix = new Matrix();
49 
50     private final Launcher mLauncher;
51     private final ListenerView mListenerView;
52     private final FloatingWidgetBackgroundView mBackgroundView;
53     private final RectF mBackgroundOffset = new RectF();
54 
55     private LauncherAppWidgetHostView mAppWidgetView;
56     private View mAppWidgetBackgroundView;
57     private RectF mBackgroundPosition;
58     private GhostView mForegroundOverlayView;
59 
60     private Runnable mEndRunnable;
61     private Runnable mFastFinishRunnable;
62     private Runnable mOnTargetChangeRunnable;
63     private boolean mAppTargetIsTranslucent;
64 
65     private float mIconOffsetY;
66 
FloatingWidgetView(Context context)67     public FloatingWidgetView(Context context) {
68         this(context, null);
69     }
70 
FloatingWidgetView(Context context, AttributeSet attrs)71     public FloatingWidgetView(Context context, AttributeSet attrs) {
72         this(context, attrs, 0);
73     }
74 
FloatingWidgetView(Context context, AttributeSet attrs, int defStyleAttr)75     public FloatingWidgetView(Context context, AttributeSet attrs, int defStyleAttr) {
76         super(context, attrs, defStyleAttr);
77         mLauncher = Launcher.getLauncher(context);
78         mListenerView = new ListenerView(context, attrs);
79         mBackgroundView = new FloatingWidgetBackgroundView(context, attrs, defStyleAttr);
80         addView(mBackgroundView);
81         setWillNotDraw(false);
82     }
83 
84     @Override
onAnimationEnd(Animator animator)85     public void onAnimationEnd(Animator animator) {
86         Runnable endRunnable = mEndRunnable;
87         mEndRunnable = null;
88         if (endRunnable != null) {
89             endRunnable.run();
90         }
91     }
92 
93     @Override
onAnimationStart(Animator animator)94     public void onAnimationStart(Animator animator) {
95     }
96 
97     @Override
onAnimationCancel(Animator animator)98     public void onAnimationCancel(Animator animator) {
99     }
100 
101     @Override
onAnimationRepeat(Animator animator)102     public void onAnimationRepeat(Animator animator) {
103     }
104 
105     @Override
onAttachedToWindow()106     protected void onAttachedToWindow() {
107         super.onAttachedToWindow();
108         getViewTreeObserver().addOnGlobalLayoutListener(this);
109     }
110 
111     @Override
onDetachedFromWindow()112     protected void onDetachedFromWindow() {
113         getViewTreeObserver().removeOnGlobalLayoutListener(this);
114         super.onDetachedFromWindow();
115     }
116 
117     @Override
onGlobalLayout()118     public void onGlobalLayout() {
119         if (isUninitialized()) return;
120         positionViews();
121         if (mOnTargetChangeRunnable != null) {
122             mOnTargetChangeRunnable.run();
123         }
124     }
125 
126     /** Sets a runnable that is called on global layout change. */
setOnTargetChangeListener(Runnable onTargetChangeListener)127     public void setOnTargetChangeListener(Runnable onTargetChangeListener) {
128         mOnTargetChangeRunnable = onTargetChangeListener;
129     }
130 
131     /** Sets a runnable that is called after a call to {@link #fastFinish()}. */
setFastFinishRunnable(Runnable runnable)132     public void setFastFinishRunnable(Runnable runnable) {
133         mFastFinishRunnable = runnable;
134     }
135 
136     /** Callback at the end or early exit of the animation. */
137     @Override
fastFinish()138     public void fastFinish() {
139         if (isUninitialized()) return;
140         Runnable fastFinishRunnable = mFastFinishRunnable;
141         if (fastFinishRunnable != null) {
142             fastFinishRunnable.run();
143         }
144         Runnable endRunnable = mEndRunnable;
145         mEndRunnable = null;
146         if (endRunnable != null) {
147             endRunnable.run();
148         }
149     }
150 
init(DragLayer dragLayer, LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius, boolean appTargetIsTranslucent, int fallbackBackgroundColor)151     private void init(DragLayer dragLayer, LauncherAppWidgetHostView originalView,
152             RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius,
153             boolean appTargetIsTranslucent, int fallbackBackgroundColor) {
154         mAppWidgetView = originalView;
155         // Deferrals must begin before GhostView is created. See b/190818220
156         mAppWidgetView.beginDeferringUpdates();
157         mBackgroundPosition = widgetBackgroundPosition;
158         mAppTargetIsTranslucent = appTargetIsTranslucent;
159         mEndRunnable = () -> finish(dragLayer);
160 
161         mAppWidgetBackgroundView = RoundedCornerEnforcement.findBackground(mAppWidgetView);
162         if (mAppWidgetBackgroundView == null) {
163             mAppWidgetBackgroundView = mAppWidgetView;
164         }
165 
166         getRelativePosition(mAppWidgetBackgroundView, dragLayer, mBackgroundPosition);
167         getRelativePosition(mAppWidgetBackgroundView, mAppWidgetView, mBackgroundOffset);
168         if (!mAppTargetIsTranslucent) {
169             mBackgroundView.init(mAppWidgetView, mAppWidgetBackgroundView, windowCornerRadius,
170                     fallbackBackgroundColor);
171             // Layout call before GhostView creation so that the overlaid view isn't clipped
172             layout(0, 0, windowSize.getWidth(), windowSize.getHeight());
173             mForegroundOverlayView = GhostView.addGhost(mAppWidgetView, this);
174             positionViews();
175         }
176 
177         mListenerView.setListener(this::fastFinish);
178         dragLayer.addView(mListenerView);
179     }
180 
181     /**
182      * Updates the position and opacity of the floating widget's components.
183      *
184      * @param backgroundPosition      the new position of the widget's background relative to the
185      *                                {@link FloatingWidgetView}'s parent
186      * @param floatingWidgetAlpha     the overall opacity of the {@link FloatingWidgetView}
187      * @param foregroundAlpha         the opacity of the foreground layer
188      * @param fallbackBackgroundAlpha the opacity of the fallback background used when the App
189      *                                Widget doesn't have a background
190      * @param cornerRadiusProgress    progress of the corner radius animation, where 0 is the
191      *                                original radius and 1 is the window radius
192      */
update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha, float fallbackBackgroundAlpha, float cornerRadiusProgress)193     public void update(RectF backgroundPosition, float floatingWidgetAlpha, float foregroundAlpha,
194             float fallbackBackgroundAlpha, float cornerRadiusProgress) {
195         if (isUninitialized() || mAppTargetIsTranslucent) return;
196         setAlpha(floatingWidgetAlpha);
197         mBackgroundView.update(cornerRadiusProgress, fallbackBackgroundAlpha);
198         mAppWidgetView.setAlpha(foregroundAlpha);
199         mBackgroundPosition = backgroundPosition;
200         positionViews();
201     }
202 
203     @Override
setPositionOffsetY(float y)204     public void setPositionOffsetY(float y) {
205         mIconOffsetY = y;
206         onGlobalLayout();
207     }
208 
209     /** Sets the layout parameters of the floating view and its background view child. */
positionViews()210     private void positionViews() {
211         LayoutParams layoutParams = (LayoutParams) getLayoutParams();
212         layoutParams.setMargins(0, 0, 0, 0);
213         setLayoutParams(layoutParams);
214 
215         // FloatingWidgetView layout is forced LTR
216         mBackgroundView.setTranslationX(mBackgroundPosition.left);
217         mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY);
218         LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
219         backgroundParams.leftMargin = 0;
220         backgroundParams.topMargin = 0;
221         backgroundParams.width = (int) mBackgroundPosition.width();
222         backgroundParams.height = (int) mBackgroundPosition.height();
223         mBackgroundView.setLayoutParams(backgroundParams);
224 
225         if (mForegroundOverlayView != null) {
226             sTmpMatrix.reset();
227             float foregroundScale =
228                     mBackgroundPosition.width() / mAppWidgetBackgroundView.getWidth();
229             sTmpMatrix.setTranslate(-mBackgroundOffset.left - mAppWidgetView.getLeft(),
230                     -mBackgroundOffset.top - mAppWidgetView.getTop());
231             sTmpMatrix.postScale(foregroundScale, foregroundScale);
232             sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top
233                     + mIconOffsetY);
234             mForegroundOverlayView.setMatrix(sTmpMatrix);
235         }
236     }
237 
finish(DragLayer dragLayer)238     private void finish(DragLayer dragLayer) {
239         mAppWidgetView.setAlpha(1f);
240         GhostView.removeGhost(mAppWidgetView);
241         ((ViewGroup) dragLayer.getParent()).removeView(this);
242         dragLayer.removeView(mListenerView);
243         mBackgroundView.finish();
244         // Removing GhostView must occur before ending deferrals. See b/190818220
245         mAppWidgetView.endDeferringUpdates();
246         recycle();
247         mLauncher.getViewCache().recycleView(R.layout.floating_widget_view, this);
248     }
249 
getInitialCornerRadius()250     public float getInitialCornerRadius() {
251         return mBackgroundView.getMaximumRadius();
252     }
253 
isUninitialized()254     private boolean isUninitialized() {
255         return mForegroundOverlayView == null;
256     }
257 
recycle()258     private void recycle() {
259         mIconOffsetY = 0;
260         mEndRunnable = null;
261         mFastFinishRunnable = null;
262         mOnTargetChangeRunnable = null;
263         mBackgroundPosition = null;
264         mListenerView.setListener(null);
265         mAppWidgetView = null;
266         mForegroundOverlayView = null;
267         mAppWidgetBackgroundView = null;
268         mBackgroundView.recycle();
269     }
270 
271     /**
272      * Configures and returns a an instance of {@link FloatingWidgetView} matching the appearance of
273      * {@param originalView}.
274      *
275      * @param widgetBackgroundPosition a {@link RectF} that will be updated with the widget's
276      *                                 background bounds
277      * @param windowSize               the size of the window when launched
278      * @param windowCornerRadius       the corner radius of the window
279      */
getFloatingWidgetView(Launcher launcher, LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition, Size windowSize, float windowCornerRadius, boolean appTargetsAreTranslucent, int fallbackBackgroundColor)280     public static FloatingWidgetView getFloatingWidgetView(Launcher launcher,
281             LauncherAppWidgetHostView originalView, RectF widgetBackgroundPosition,
282             Size windowSize, float windowCornerRadius, boolean appTargetsAreTranslucent,
283             int fallbackBackgroundColor) {
284         final DragLayer dragLayer = launcher.getDragLayer();
285         ViewGroup parent = (ViewGroup) dragLayer.getParent();
286         FloatingWidgetView floatingView =
287                 launcher.getViewCache().getView(R.layout.floating_widget_view, launcher, parent);
288         floatingView.recycle();
289 
290         floatingView.init(dragLayer, originalView, widgetBackgroundPosition, windowSize,
291                 windowCornerRadius, appTargetsAreTranslucent, fallbackBackgroundColor);
292         parent.addView(floatingView);
293         return floatingView;
294     }
295 
296     /**
297      * Extract a background color from a target's task description, or fall back to the given
298      * context's theme background color.
299      */
getDefaultBackgroundColor( Context context, RemoteAnimationTargetCompat target)300     public static int getDefaultBackgroundColor(
301             Context context, RemoteAnimationTargetCompat target) {
302         return (target != null && target.taskInfo.taskDescription != null)
303                 ? target.taskInfo.taskDescription.getBackgroundColor()
304                 : Themes.getColorBackground(context);
305     }
306 
getRelativePosition(View descendant, View ancestor, RectF position)307     private static void getRelativePosition(View descendant, View ancestor, RectF position) {
308         float[] points = new float[]{0, 0, descendant.getWidth(), descendant.getHeight()};
309         Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, points,
310                 false /* includeRootScroll */, true /* ignoreTransform */);
311         position.set(
312                 Math.min(points[0], points[2]),
313                 Math.min(points[1], points[3]),
314                 Math.max(points[0], points[2]),
315                 Math.max(points[1], points[3]));
316     }
317 }
318