| /* |
| * 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); |
| } |
| } |