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