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