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