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