blob: 154826889108be3df20b905a6a65f189e93a3a1a [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.views;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Outline;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.RemoteViews.RemoteViewOutlineProvider;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.RoundedCornerEnforcement;
import java.util.stream.IntStream;
/**
* Mimics the appearance of the background view of a {@link LauncherAppWidgetHostView} through a
* an App Widget activity launch animation.
*/
@TargetApi(Build.VERSION_CODES.S)
final class FloatingWidgetBackgroundView extends View {
private final ColorDrawable mFallbackDrawable = new ColorDrawable();
private final DrawableProperties mForegroundProperties = new DrawableProperties();
private final DrawableProperties mBackgroundProperties = new DrawableProperties();
private Drawable mOriginalForeground;
private Drawable mOriginalBackground;
private float mFinalRadius;
private float mInitialOutlineRadius;
private float mOutlineRadius;
private boolean mIsUsingFallback;
private View mSourceView;
FloatingWidgetBackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
}
});
setClipToOutline(true);
}
void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
int fallbackBackgroundColor) {
mFinalRadius = finalRadius;
mSourceView = backgroundView;
mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
mIsUsingFallback = false;
if (isSupportedDrawable(backgroundView.getForeground())) {
mOriginalForeground = backgroundView.getForeground();
mForegroundProperties.init(
mOriginalForeground.getConstantState().newDrawable().mutate());
setForeground(mForegroundProperties.mDrawable);
Drawable clipPlaceholder =
mOriginalForeground.getConstantState().newDrawable().mutate();
clipPlaceholder.setAlpha(0);
mSourceView.setForeground(clipPlaceholder);
}
if (isSupportedDrawable(backgroundView.getBackground())) {
mOriginalBackground = backgroundView.getBackground();
mBackgroundProperties.init(
mOriginalBackground.getConstantState().newDrawable().mutate());
setBackground(mBackgroundProperties.mDrawable);
Drawable clipPlaceholder =
mOriginalBackground.getConstantState().newDrawable().mutate();
clipPlaceholder.setAlpha(0);
mSourceView.setBackground(clipPlaceholder);
} else if (mOriginalForeground == null) {
mFallbackDrawable.setColor(fallbackBackgroundColor);
setBackground(mFallbackDrawable);
mIsUsingFallback = true;
}
}
/** Update the animated properties of the drawables. */
void update(float cornerRadiusProgress, float fallbackAlpha) {
if (isUninitialized()) return;
mOutlineRadius = mInitialOutlineRadius + (mFinalRadius - mInitialOutlineRadius)
* cornerRadiusProgress;
mForegroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
mBackgroundProperties.updateDrawable(mFinalRadius, cornerRadiusProgress);
setAlpha(mIsUsingFallback ? fallbackAlpha : 1f);
}
/** Restores the drawables to the source view. */
void finish() {
if (isUninitialized()) return;
if (mOriginalForeground != null) mSourceView.setForeground(mOriginalForeground);
if (mOriginalBackground != null) mSourceView.setBackground(mOriginalBackground);
}
void recycle() {
mSourceView = null;
mOriginalForeground = null;
mOriginalBackground = null;
mOutlineRadius = 0;
mFinalRadius = 0;
setForeground(null);
setBackground(null);
}
/** Get the largest of drawable corner radii or background view outline radius. */
float getMaximumRadius() {
if (isUninitialized()) return 0;
return Math.max(mInitialOutlineRadius, Math.max(getMaxRadius(mOriginalForeground),
getMaxRadius(mOriginalBackground)));
}
private boolean isUninitialized() {
return mSourceView == null;
}
/** Returns the maximum corner radius of {@param drawable}. */
private static float getMaxRadius(Drawable drawable) {
if (!(drawable instanceof GradientDrawable)) return 0;
float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
double radiiMax = cornerRadii == null ? 0 : IntStream.range(0, cornerRadii.length)
.mapToDouble(i -> cornerRadii[i]).max().orElse(0);
return Math.max(cornerRadius, (float) radiiMax);
}
/** Returns whether the given drawable type is supported. */
private static boolean isSupportedDrawable(Drawable drawable) {
return drawable instanceof ColorDrawable || (drawable instanceof GradientDrawable
&& ((GradientDrawable) drawable).getShape() == GradientDrawable.RECTANGLE);
}
/** Corner radius from source view's outline, or enforced view. */
private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
if (RoundedCornerEnforcement.isRoundedCornerEnabled()
&& hostView.hasEnforcedCornerRadius()) {
return hostView.getEnforcedCornerRadius();
} else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
&& v.getClipToOutline()) {
return ((RemoteViewOutlineProvider) v.getOutlineProvider()).getRadius();
}
return 0;
}
/** Stores and modifies a drawable's properties through an animation. */
private static class DrawableProperties {
private Drawable mDrawable;
private float mOriginalRadius;
private float[] mOriginalRadii;
private final float[] mTmpRadii = new float[8];
/** Store a drawable's animated properties. */
void init(Drawable drawable) {
mDrawable = drawable;
if (!(drawable instanceof GradientDrawable)) return;
mOriginalRadius = ((GradientDrawable) drawable).getCornerRadius();
mOriginalRadii = ((GradientDrawable) drawable).getCornerRadii();
}
/**
* Update the drawable for the given animation state.
*
* @param finalRadius the radius of each corner when {@param progress} is 1
* @param progress the linear progress of the corner radius from its original value to
* {@param finalRadius}
*/
void updateDrawable(float finalRadius, float progress) {
if (!(mDrawable instanceof GradientDrawable)) return;
GradientDrawable d = (GradientDrawable) mDrawable;
if (mOriginalRadii != null) {
for (int i = 0; i < mOriginalRadii.length; i++) {
mTmpRadii[i] = mOriginalRadii[i] + (finalRadius - mOriginalRadii[i]) * progress;
}
d.setCornerRadii(mTmpRadii);
} else {
d.setCornerRadius(mOriginalRadius + (finalRadius - mOriginalRadius) * progress);
}
}
}
}