/*
* 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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
/**
* Wraps a notification view inflated from a template.
*/
public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
private final int mIconBackgroundDarkColor;
private final Interpolator mLinearOutSlowInInterpolator;
private int mIconBackgroundColor;
private ViewInvertHelper mInvertHelper;
private ImageView mIcon;
protected ImageView mPicture;
/** Whether the icon needs to be forced grayscale when in dark mode. */
private boolean mIconForceGraysaleWhenDark;
protected NotificationTemplateViewWrapper(Context ctx, View view) {
super(view);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mIconBackgroundDarkColor =
ctx.getColor(R.color.doze_small_icon_background_color);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
resolveViews();
}
private void resolveViews() {
View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
mInvertHelper = mainColumn != null
? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
: null;
ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
mIcon = resolveIcon(largeIcon, rightIcon);
mPicture = resolvePicture(largeIcon);
mIconBackgroundColor = resolveBackgroundColor(mIcon);
// If the icon already has a color filter, we assume that we already forced the icon to be
// white when we created the notification.
final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
}
private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
: rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
: null;
}
private ImageView resolvePicture(ImageView largeIcon) {
return largeIcon != null && largeIcon.getBackground() == null
? largeIcon
: null;
}
private int resolveBackgroundColor(ImageView icon) {
if (icon != null && icon.getBackground() != null) {
ColorFilter filter = icon.getBackground().getColorFilter();
if (filter instanceof PorterDuffColorFilter) {
return ((PorterDuffColorFilter) filter).getColor();
}
}
return 0;
}
@Override
public void notifyContentUpdated() {
super.notifyContentUpdated();
// Reinspect the notification.
resolveViews();
}
@Override
public void setDark(boolean dark, boolean fade, long delay) {
if (mInvertHelper != null) {
if (fade) {
mInvertHelper.fade(dark, delay);
} else {
mInvertHelper.update(dark);
}
}
if (mIcon != null) {
if (fade) {
fadeIconColorFilter(mIcon, dark, delay);
fadeIconAlpha(mIcon, dark, delay);
if (!mIconForceGraysaleWhenDark) {
fadeGrayscale(mIcon, dark, delay);
}
} else {
updateIconColorFilter(mIcon, dark);
updateIconAlpha(mIcon, dark);
if (!mIconForceGraysaleWhenDark) {
updateGrayscale(mIcon, dark);
}
}
}
setPictureGrayscale(dark, fade, delay);
}
protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
if (mPicture != null) {
if (fade) {
fadeGrayscale(mPicture, grayscale, delay);
} else {
updateGrayscale(mPicture, grayscale);
}
}
}
private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
boolean dark, long delay, Animator.AnimatorListener listener) {
float startIntensity = dark ? 0f : 1f;
float endIntensity = dark ? 1f : 0f;
ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
animator.addUpdateListener(updateListener);
animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
animator.setInterpolator(mLinearOutSlowInInterpolator);
animator.setStartDelay(delay);
if (listener != null) {
animator.addListener(listener);
}
animator.start();
}
private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateIconColorFilter(target, (Float) animation.getAnimatedValue());
}
}, dark, delay, null /* listener */);
}
private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (float) animation.getAnimatedValue();
target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
}
}, dark, delay, null /* listener */);
}
protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateGrayscaleMatrix((float) animation.getAnimatedValue());
target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
}
}, dark, delay, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!dark) {
target.setColorFilter(null);
}
}
});
}
private void updateIconColorFilter(ImageView target, boolean dark) {
updateIconColorFilter(target, dark ? 1f : 0f);
}
private void updateIconColorFilter(ImageView target, float intensity) {
int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
mIconColorFilter.setColor(color);
Drawable background = target.getBackground();
// The background might be null for legacy notifications. Also, the notification might have
// been modified during the animation, so background might be null here.
if (background != null) {
background.mutate().setColorFilter(mIconColorFilter);
}
}
private void updateIconAlpha(ImageView target, boolean dark) {
target.setImageAlpha(dark ? mIconDarkAlpha : 255);
}
protected void updateGrayscale(ImageView target, boolean dark) {
if (dark) {
updateGrayscaleMatrix(1f);
target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
} else {
target.setColorFilter(null);
}
}
private void updateGrayscaleMatrix(float intensity) {
mGrayscaleColorMatrix.setSaturation(1 - intensity);
}
private static int interpolateColor(int source, int target, float t) {
int aSource = Color.alpha(source);
int rSource = Color.red(source);
int gSource = Color.green(source);
int bSource = Color.blue(source);
int aTarget = Color.alpha(target);
int rTarget = Color.red(target);
int gTarget = Color.green(target);
int bTarget = Color.blue(target);
return Color.argb(
(int) (aSource * (1f - t) + aTarget * t),
(int) (rSource * (1f - t) + rTarget * t),
(int) (gSource * (1f - t) + gTarget * t),
(int) (bSource * (1f - t) + bTarget * t));
}
}