• 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.annotation.TargetApi;
19 import android.content.Context;
20 import android.graphics.Outline;
21 import android.graphics.drawable.ColorDrawable;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.GradientDrawable;
24 import android.os.Build;
25 import android.util.AttributeSet;
26 import android.view.View;
27 import android.view.ViewOutlineProvider;
28 import android.widget.RemoteViews.RemoteViewOutlineProvider;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 
33 import com.android.launcher3.R;
34 import com.android.launcher3.widget.LauncherAppWidgetHostView;
35 
36 import java.util.stream.IntStream;
37 
38 /**
39  * Mimics the appearance of the background view of a {@link LauncherAppWidgetHostView} through a
40  * an App Widget activity launch animation.
41  */
42 @TargetApi(Build.VERSION_CODES.S)
43 final class FloatingWidgetBackgroundView extends View {
44     private final ColorDrawable mFallbackDrawable = new ColorDrawable();
45     private final DrawableProperties mForegroundProperties = new DrawableProperties();
46     private final DrawableProperties mBackgroundProperties = new DrawableProperties();
47 
48     @Nullable
49     private Drawable mOriginalForeground;
50     @Nullable
51     private Drawable mOriginalBackground;
52     private float mFinalRadius;
53     private float mInitialOutlineRadius;
54     private float mOutlineRadius;
55     private boolean mIsUsingFallback;
56     private View mSourceView;
57 
FloatingWidgetBackgroundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)58     FloatingWidgetBackgroundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
59         super(context, attrs, defStyleAttr);
60         setOutlineProvider(new ViewOutlineProvider() {
61             @Override
62             public void getOutline(View view, Outline outline) {
63                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
64             }
65         });
66         setClipToOutline(true);
67     }
68 
init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView, float finalRadius, int fallbackBackgroundColor)69     void init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView,
70             float finalRadius, int fallbackBackgroundColor) {
71         mFinalRadius = finalRadius;
72         mSourceView = backgroundView;
73         mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
74         mIsUsingFallback = false;
75         if (isSupportedDrawable(backgroundView.getForeground())) {
76             if (backgroundView.getTag(R.id.saved_floating_widget_foreground) == null) {
77                 mOriginalForeground = backgroundView.getForeground();
78                 backgroundView.setTag(R.id.saved_floating_widget_foreground, mOriginalForeground);
79             } else {
80                 mOriginalForeground = (Drawable) backgroundView.getTag(
81                         R.id.saved_floating_widget_foreground);
82             }
83             mForegroundProperties.init(
84                     mOriginalForeground.getConstantState().newDrawable().mutate());
85             setForeground(mForegroundProperties.mDrawable);
86             Drawable clipPlaceholder =
87                     mOriginalForeground.getConstantState().newDrawable().mutate();
88             clipPlaceholder.setAlpha(0);
89             mSourceView.setForeground(clipPlaceholder);
90         }
91         if (isSupportedDrawable(backgroundView.getBackground())) {
92             if (backgroundView.getTag(R.id.saved_floating_widget_background) == null) {
93                 mOriginalBackground = backgroundView.getBackground();
94                 backgroundView.setTag(R.id.saved_floating_widget_background, mOriginalBackground);
95             } else {
96                 mOriginalBackground = (Drawable) backgroundView.getTag(
97                         R.id.saved_floating_widget_background);
98             }
99             mBackgroundProperties.init(
100                     mOriginalBackground.getConstantState().newDrawable().mutate());
101             setBackground(mBackgroundProperties.mDrawable);
102             Drawable clipPlaceholder =
103                     mOriginalBackground.getConstantState().newDrawable().mutate();
104             clipPlaceholder.setAlpha(0);
105             mSourceView.setBackground(clipPlaceholder);
106         } else if (mOriginalForeground == null) {
107             mFallbackDrawable.setColor(fallbackBackgroundColor);
108             setBackground(mFallbackDrawable);
109             mIsUsingFallback = true;
110         }
111     }
112 
113     /** Update the animated properties of the drawables. */
update(float cornerRadiusProgress, float fallbackAlpha)114     void update(float cornerRadiusProgress, float fallbackAlpha) {
115         if (isUninitialized()) return;
116         mOutlineRadius = mInitialOutlineRadius + (mFinalRadius - mInitialOutlineRadius)
117                 * cornerRadiusProgress;
118         mForegroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
119         mBackgroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
120         setAlpha(mIsUsingFallback ? fallbackAlpha : 1f);
121     }
122 
123     /** Restores the drawables to the source view. */
finish()124     void finish() {
125         if (isUninitialized()) return;
126         if (mOriginalForeground != null) mSourceView.setForeground(mOriginalForeground);
127         if (mOriginalBackground != null) mSourceView.setBackground(mOriginalBackground);
128     }
129 
recycle()130     void recycle() {
131         if (mSourceView != null) {
132             mSourceView.setTag(R.id.saved_floating_widget_foreground, null);
133             mSourceView.setTag(R.id.saved_floating_widget_background, null);
134         }
135         mSourceView = null;
136         mOriginalForeground = null;
137         mOriginalBackground = null;
138         mOutlineRadius = 0;
139         mFinalRadius = 0;
140         setForeground(null);
141         setBackground(null);
142     }
143 
144     /** Get the largest of drawable corner radii or background view outline radius. */
getMaximumRadius()145     float getMaximumRadius() {
146         if (isUninitialized()) return 0;
147         return Math.max(mInitialOutlineRadius, Math.max(getMaxRadius(mOriginalForeground),
148                 getMaxRadius(mOriginalBackground)));
149     }
150 
isUninitialized()151     private boolean isUninitialized() {
152         return mSourceView == null;
153     }
154 
155     /** Returns the maximum corner radius of {@param drawable}. */
getMaxRadius(@ullable Drawable drawable)156     private static float getMaxRadius(@Nullable Drawable drawable) {
157         if (!(drawable instanceof GradientDrawable)) return 0;
158         float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
159         float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
160         double radiiMax = cornerRadii == null ? 0 : IntStream.range(0, cornerRadii.length)
161                 .mapToDouble(i -> cornerRadii[i]).max().orElse(0);
162         return Math.max(cornerRadius, (float) radiiMax);
163     }
164 
165     /** Returns whether the given drawable type is supported. */
isSupportedDrawable(Drawable drawable)166     private static boolean isSupportedDrawable(Drawable drawable) {
167         return drawable instanceof ColorDrawable || (drawable instanceof GradientDrawable
168                 && ((GradientDrawable) drawable).getShape() == GradientDrawable.RECTANGLE);
169     }
170 
171     /** Corner radius from source view's outline, or enforced view. */
getOutlineRadius(LauncherAppWidgetHostView hostView, View v)172     private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
173         if (hostView.hasEnforcedCornerRadius()) {
174             return hostView.getEnforcedCornerRadius();
175         } else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
176                 && v.getClipToOutline()) {
177             return ((RemoteViewOutlineProvider) v.getOutlineProvider()).getRadius();
178         }
179         return 0;
180     }
181 
182     /** Stores and modifies a drawable's properties through an animation. */
183     private static class DrawableProperties {
184         @Nullable
185         private Drawable mDrawable;
186         private float mOriginalRadius;
187         @Nullable
188         private float[] mOriginalRadii;
189         private final float[] mTmpRadii = new float[8];
190 
191         /** Store a drawable's animated properties. */
init(Drawable drawable)192         void init(Drawable drawable) {
193             mDrawable = drawable;
194             if (!(drawable instanceof GradientDrawable)) return;
195             mOriginalRadius = ((GradientDrawable) drawable).getCornerRadius();
196             mOriginalRadii = ((GradientDrawable) drawable).getCornerRadii();
197         }
198 
199         /**
200          * Update the drawable for the given animation state.
201          *
202          * @param finalRadius the radius of each corner when {@param progress} is 1
203          * @param progress    the linear progress of the corner radius from its original value to
204          *                    {@param finalRadius}
205          */
updateDrawable(float finalRadius, float progress)206         void updateDrawable(float finalRadius, float progress) {
207             if (!(mDrawable instanceof GradientDrawable)) return;
208             GradientDrawable d = (GradientDrawable) mDrawable;
209             if (mOriginalRadii != null) {
210                 for (int i = 0; i < mOriginalRadii.length; i++) {
211                     mTmpRadii[i] = mOriginalRadii[i] + (finalRadius - mOriginalRadii[i]) * progress;
212                 }
213                 d.setCornerRadii(mTmpRadii);
214             } else {
215                 d.setCornerRadius(mOriginalRadius + (finalRadius - mOriginalRadius) * progress);
216             }
217         }
218     }
219 }
220