blob: 7794d5ba9a0f9a5441ae477a2293f61d359abcf9 [file] [log] [blame]
/*
* Copyright (C) 2015 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;
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.ColorMatrixColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.util.Stack;
/**
* Wraps a notification header view.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
private final int mIconDarkColor = 0xffffffff;
protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
protected int mColor;
private ImageView mIcon;
private ImageView mExpandButton;
private NotificationHeaderView mNotificationHeader;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(view, row);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
resolveHeaderViews();
updateInvertHelper();
}
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
}
private int resolveColor(ImageView icon) {
if (icon != null && icon.getDrawable() != null) {
ColorFilter filter = icon.getDrawable().getColorFilter();
if (filter instanceof PorterDuffColorFilter) {
return ((PorterDuffColorFilter) filter).getColor();
}
}
return 0;
}
@Override
public void notifyContentUpdated(StatusBarNotification notification) {
super.notifyContentUpdated(notification);
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
// Reinspect the notification.
resolveHeaderViews();
updateInvertHelper();
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews();
for (int i = 0; i < previousViews.size(); i++) {
View view = previousViews.valueAt(i);
if (!currentViews.contains(view)) {
mTransformationHelper.resetTransformedView(view);
}
}
}
/**
* Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
* child is faded automatically and doesn't have to be manually added.
* The keys used for the views are the ids.
*/
private void addRemainingTransformTypes() {
mTransformationHelper.addRemainingTransformTypes(mView);
}
/**
* Since we are deactivating the clipping when transforming the ImageViews don't get clipped
* anymore during these transitions. We can avoid that by using
* {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
*/
private void updateCropToPaddingForImageViews() {
Stack<View> stack = new Stack<>();
stack.push(mView);
while (!stack.isEmpty()) {
View child = stack.pop();
if (child instanceof ImageView) {
((ImageView) child).setCropToPadding(true);
} else if (child instanceof ViewGroup){
ViewGroup group = (ViewGroup) child;
for (int i = 0; i < group.getChildCount(); i++) {
stack.push(group.getChildAt(i));
}
}
}
}
protected void updateInvertHelper() {
mInvertHelper.clearTargets();
for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
View child = mNotificationHeader.getChildAt(i);
if (child != mIcon) {
mInvertHelper.addTarget(child);
}
}
}
protected void updateTransformedTypes() {
mTransformationHelper.reset();
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
mNotificationHeader);
}
@Override
public void setDark(boolean dark, boolean fade, long delay) {
if (dark == mDark && mDarkInitialized) {
return;
}
super.setDark(dark, fade, delay);
if (fade) {
mInvertHelper.fade(dark, delay);
} else {
mInvertHelper.update(dark);
}
if (mIcon != null && !mRow.isChildInGroup()) {
// We don't update the color for children views / their icon is invisible anyway.
// It also may lead to bugs where the icon isn't correctly greyed out.
boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
!= NotificationHeaderView.NO_COLOR;
if (fade) {
if (hadColorFilter) {
fadeIconColorFilter(mIcon, dark, delay);
fadeIconAlpha(mIcon, dark, delay);
} else {
fadeGrayscale(mIcon, dark, delay);
}
} else {
if (hadColorFilter) {
updateIconColorFilter(mIcon, dark);
updateIconAlpha(mIcon, dark);
} else {
updateGrayscale(mIcon, dark);
}
}
}
}
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(mColor, mIconDarkColor, intensity);
mIconColorFilter.setColor(color);
Drawable iconDrawable = target.getDrawable();
// Also, the notification might have been modified during the animation, so background
// might be null here.
if (iconDrawable != null) {
iconDrawable.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);
}
}
@Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
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));
}
@Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
}
@Override
public TransformState getCurrentState(int fadingView) {
return mTransformationHelper.getCurrentState(fadingView);
}
@Override
public void transformTo(TransformableView notification, Runnable endRunnable) {
mTransformationHelper.transformTo(notification, endRunnable);
}
@Override
public void transformTo(TransformableView notification, float transformationAmount) {
mTransformationHelper.transformTo(notification, transformationAmount);
}
@Override
public void transformFrom(TransformableView notification) {
mTransformationHelper.transformFrom(notification);
}
@Override
public void transformFrom(TransformableView notification, float transformationAmount) {
mTransformationHelper.transformFrom(notification, transformationAmount);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
mTransformationHelper.setVisible(visible);
}
}