/* * 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.systemui.privacy.television; import android.animation.Animator; import android.animation.AnimatorInflater; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.Log; import android.util.MathUtils; import android.view.Gravity; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.R; /** * Drawable that can go from being the background of the privacy icons to a small dot. * The icons are not included. */ public class PrivacyChipDrawable extends Drawable { private static final String TAG = PrivacyChipDrawable.class.getSimpleName(); private static final boolean DEBUG = false; private final Paint mChipPaint; private final Paint mBgPaint; private final Rect mTmpRect = new Rect(); private final Rect mBgRect = new Rect(); private final RectF mTmpRectF = new RectF(); private final Path mPath = new Path(); private final Animator mCollapse; private final Animator mExpand; private final int mLayoutDirection; private final int mBgWidth; private final int mBgHeight; private final int mBgRadius; private final int mDotSize; private final float mExpandedChipRadius; private final float mCollapsedChipRadius; private final boolean mCollapseToDot; private boolean mIsExpanded = true; private float mCollapseProgress = 0f; public PrivacyChipDrawable(Context context, int chipColorRes, boolean collapseToDot) { mCollapseToDot = collapseToDot; mChipPaint = new Paint(); mChipPaint.setStyle(Paint.Style.FILL); mChipPaint.setColor(context.getColor(chipColorRes)); mChipPaint.setFlags(Paint.ANTI_ALIAS_FLAG); mBgPaint = new Paint(); mBgPaint.setStyle(Paint.Style.FILL); mBgPaint.setColor(context.getColor(R.color.privacy_chip_dot_bg_tint)); mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG); Resources res = context.getResources(); mLayoutDirection = res.getConfiguration().getLayoutDirection(); mBgWidth = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_width); mBgHeight = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_height); mBgRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_radius); mDotSize = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_size); mExpandedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_radius); mCollapsedChipRadius = res.getDimensionPixelSize(R.dimen.privacy_chip_dot_radius); mExpand = AnimatorInflater.loadAnimator(context, R.anim.tv_privacy_chip_expand); mExpand.setTarget(this); mCollapse = AnimatorInflater.loadAnimator(context, R.anim.tv_privacy_chip_collapse); mCollapse.setTarget(this); } /** * @return how far the chip is currently collapsed. * @see #setCollapseProgress(float) */ @Keep public float getCollapseProgress() { return mCollapseProgress; } /** * Sets the collapsing progress of the chip to its collapsed state. * @param pct How far the chip is collapsed, in the range 0-1. * 0=fully expanded, 1=fully collapsed. */ @Keep public void setCollapseProgress(float pct) { mCollapseProgress = pct; invalidateSelf(); } @Override public void draw(@NonNull Canvas canvas) { if (mCollapseProgress > 0f) { // draw background getBackgroundBounds(mBgRect); mTmpRectF.set(mBgRect); canvas.drawRoundRect(mTmpRectF, mBgRadius, mBgRadius, mBgPaint); } getForegroundBounds(mTmpRectF); float radius = MathUtils.lerp( mExpandedChipRadius, mCollapseToDot ? mCollapsedChipRadius : mBgRadius, mCollapseProgress); canvas.drawRoundRect(mTmpRectF, radius, radius, mChipPaint); } private void getBackgroundBounds(Rect out) { Rect bounds = getBounds(); Gravity.apply(Gravity.END, mBgWidth, mBgHeight, bounds, out, mLayoutDirection); } private void getCollapsedForegroundBounds(Rect out) { Rect bounds = getBounds(); getBackgroundBounds(mBgRect); if (mCollapseToDot) { Gravity.apply(Gravity.CENTER, mDotSize, mDotSize, mBgRect, out); } else { out.set(bounds.left, mBgRect.top, bounds.right, mBgRect.bottom); } } private void getForegroundBounds(RectF out) { Rect bounds = getBounds(); getCollapsedForegroundBounds(mTmpRect); lerpRect(bounds, mTmpRect, mCollapseProgress, out); } private void lerpRect(Rect start, Rect stop, float amount, RectF out) { float left = MathUtils.lerp(start.left, stop.left, amount); float top = MathUtils.lerp(start.top, stop.top, amount); float right = MathUtils.lerp(start.right, stop.right, amount); float bottom = MathUtils.lerp(start.bottom, stop.bottom, amount); out.set(left, top, right, bottom); } /** * Clips the given canvas to this chip's foreground shape. * @param canvas Canvas to clip. */ public void clipToForeground(Canvas canvas) { getForegroundBounds(mTmpRectF); float radius = MathUtils.lerp( mExpandedChipRadius, mCollapseToDot ? mCollapsedChipRadius : mBgRadius, mCollapseProgress); mPath.reset(); mPath.addRoundRect(mTmpRectF, radius, radius, Path.Direction.CW); canvas.clipPath(mPath); } @Override protected void onBoundsChange(@NonNull Rect bounds) { super.onBoundsChange(bounds); invalidateSelf(); } @Override public void setAlpha(int alpha) { mChipPaint.setAlpha(alpha); mBgPaint.setAlpha(alpha); } /** * Transitions to a full chip. * * @param animate Whether to animate the change to a full chip, or expand instantly. */ public void expand(boolean animate) { if (DEBUG) Log.d(TAG, "expanding"); if (mIsExpanded) { return; } mIsExpanded = true; if (animate) { mCollapse.cancel(); mExpand.start(); } else { mCollapseProgress = 0f; invalidateSelf(); } } /** * Starts the animation to a dot. */ public void collapse() { if (DEBUG) Log.d(TAG, "collapsing"); if (!mIsExpanded) { return; } mIsExpanded = false; mExpand.cancel(); mCollapse.start(); } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { // no-op } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } }