blob: 08911d4f8c328cf12a6e5cdf1296cc14ddd7e836 [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.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;
}
}