blob: 647505cea71bd44fd95cf7e131768869f124ce81 [file] [log] [blame]
/*
* Copyright (C) 2014 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.statusbar.notification.row;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View implements Dumpable {
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
private int mClipBottomAmount;
private int mTintColor;
@Nullable private Integer mRippleColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
private boolean mBottomAmountClips = true;
private int mActualHeight = -1;
private int mActualWidth = -1;
private boolean mExpandAnimationRunning;
private int mExpandAnimationWidth = -1;
private int mExpandAnimationHeight = -1;
private int mDrawableAlpha = 255;
private boolean mIsPressedAllowed;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
mDontModifyCorners = getResources().getBoolean(
R.bool.config_clipNotificationsToOutline);
}
@Override
protected void onDraw(Canvas canvas) {
if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
canvas.save();
if (!mExpandAnimationRunning) {
canvas.clipRect(0, mClipTopAmount, getWidth(),
getActualHeight() - mClipBottomAmount);
}
draw(canvas, mBackground);
canvas.restore();
}
}
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
int top = 0;
int bottom = getActualHeight();
if (mBottomIsRounded
&& mBottomAmountClips
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
final boolean isRtl = isLayoutRtl();
final int width = getWidth();
final int actualWidth = getActualWidth();
int left = isRtl ? width - actualWidth : 0;
int right = isRtl ? width : actualWidth;
if (mExpandAnimationRunning) {
// Horizontally center this background view inside of the container
left = (int) ((width - actualWidth) / 2.0f);
right = (int) (left + actualWidth);
}
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mBackground;
}
@Override
protected void drawableStateChanged() {
setState(getDrawableState());
}
@Override
public void drawableHotspotChanged(float x, float y) {
if (mBackground != null) {
mBackground.setHotspot(x, y);
}
}
/**
* Sets a background drawable. As we need to change our bounds independently of layout, we need
* the notion of a background independently of the regular View background..
*/
public void setCustomBackground(Drawable background) {
if (mBackground != null) {
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
mBackground = background;
mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
setTint(mTintColor);
}
if (mBackground instanceof RippleDrawable) {
((RippleDrawable) mBackground).setForceSoftware(true);
}
updateBackgroundRadii();
invalidate();
}
public void setCustomBackground(int drawableResId) {
final Drawable d = mContext.getDrawable(drawableResId);
setCustomBackground(d);
}
public void setTint(int tintColor) {
if (tintColor != 0) {
mBackground.setColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
} else {
mBackground.clearColorFilter();
}
mTintColor = tintColor;
invalidate();
}
public void setActualHeight(int actualHeight) {
if (mExpandAnimationRunning) {
return;
}
mActualHeight = actualHeight;
invalidate();
}
private int getActualHeight() {
if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
return mExpandAnimationHeight;
} else if (mActualHeight > -1) {
return mActualHeight;
}
return getHeight();
}
public void setActualWidth(int actualWidth) {
mActualWidth = actualWidth;
}
private int getActualWidth() {
if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
return mExpandAnimationWidth;
} else if (mActualWidth > -1) {
return mActualWidth;
}
return getWidth();
}
public void setClipTopAmount(int clipTopAmount) {
mClipTopAmount = clipTopAmount;
invalidate();
}
public void setClipBottomAmount(int clipBottomAmount) {
mClipBottomAmount = clipBottomAmount;
invalidate();
}
@Override
public boolean hasOverlappingRendering() {
// Prevents this view from creating a layer when alpha is animating.
return false;
}
public void setState(int[] drawableState) {
if (mBackground != null && mBackground.isStateful()) {
if (!mIsPressedAllowed) {
drawableState = ArrayUtils.removeInt(drawableState,
com.android.internal.R.attr.state_pressed);
}
mBackground.setState(drawableState);
}
}
public void setRippleColor(int color) {
if (mBackground instanceof RippleDrawable) {
RippleDrawable ripple = (RippleDrawable) mBackground;
ripple.setColor(ColorStateList.valueOf(color));
mRippleColor = color;
} else {
mRippleColor = null;
}
}
public void setDrawableAlpha(int drawableAlpha) {
mDrawableAlpha = drawableAlpha;
if (mExpandAnimationRunning) {
return;
}
mBackground.setAlpha(drawableAlpha);
}
/**
* Sets the current top and bottom radius for this background.
*/
public void setRadius(float topRoundness, float bottomRoundness) {
if (topRoundness == mCornerRadii[0] && bottomRoundness == mCornerRadii[4]) {
return;
}
mBottomIsRounded = bottomRoundness != 0.0f;
mCornerRadii[0] = topRoundness;
mCornerRadii[1] = topRoundness;
mCornerRadii[2] = topRoundness;
mCornerRadii[3] = topRoundness;
mCornerRadii[4] = bottomRoundness;
mCornerRadii[5] = bottomRoundness;
mCornerRadii[6] = bottomRoundness;
mCornerRadii[7] = bottomRoundness;
updateBackgroundRadii();
}
public void setBottomAmountClips(boolean clips) {
if (clips != mBottomAmountClips) {
mBottomAmountClips = clips;
invalidate();
}
}
private void updateBackgroundRadii() {
if (mDontModifyCorners) {
return;
}
if (mBackground instanceof LayerDrawable) {
GradientDrawable gradientDrawable =
(GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
gradientDrawable.setCornerRadii(mCornerRadii);
}
}
/** Set the current expand animation size. */
public void setExpandAnimationSize(int width, int height) {
mExpandAnimationHeight = height;
mExpandAnimationWidth = width;
invalidate();
}
public void setExpandAnimationRunning(boolean running) {
mExpandAnimationRunning = running;
if (mBackground instanceof LayerDrawable) {
GradientDrawable gradientDrawable =
(GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
// Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
// spot during animation anyways.
gradientDrawable.setAntiAlias(!running);
}
if (!mExpandAnimationRunning) {
setDrawableAlpha(mDrawableAlpha);
}
invalidate();
}
public void setPressedAllowed(boolean allowed) {
mIsPressedAllowed = allowed;
}
@Override
public void dump(PrintWriter pw, @NonNull String[] args) {
pw.println("mDontModifyCorners: " + mDontModifyCorners);
pw.println("mClipTopAmount: " + mClipTopAmount);
pw.println("mClipBottomAmount: " + mClipBottomAmount);
pw.println("mCornerRadii: " + Arrays.toString(mCornerRadii));
pw.println("mBottomIsRounded: " + mBottomIsRounded);
pw.println("mBottomAmountClips: " + mBottomAmountClips);
pw.println("mActualWidth: " + mActualWidth);
pw.println("mActualHeight: " + mActualHeight);
pw.println("mTintColor: " + hexColorString(mTintColor));
pw.println("mRippleColor: " + hexColorString(mRippleColor));
pw.println("mBackground: " + mBackground);
}
}