Home | History | Annotate | Download | only in notification
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.systemui.statusbar.notification;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ValueAnimator;
     22 import android.content.Context;
     23 import android.graphics.Color;
     24 import android.graphics.ColorFilter;
     25 import android.graphics.ColorMatrixColorFilter;
     26 import android.graphics.PorterDuff;
     27 import android.graphics.PorterDuffColorFilter;
     28 import android.graphics.drawable.Drawable;
     29 import android.service.notification.StatusBarNotification;
     30 import android.util.ArraySet;
     31 import android.view.NotificationHeaderView;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.ImageView;
     35 
     36 import com.android.systemui.R;
     37 import com.android.systemui.ViewInvertHelper;
     38 import com.android.systemui.statusbar.ExpandableNotificationRow;
     39 import com.android.systemui.statusbar.TransformableView;
     40 import com.android.systemui.statusbar.ViewTransformationHelper;
     41 import com.android.systemui.statusbar.phone.NotificationPanelView;
     42 
     43 import java.util.Stack;
     44 
     45 /**
     46  * Wraps a notification header view.
     47  */
     48 public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
     49 
     50     private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
     51             0, PorterDuff.Mode.SRC_ATOP);
     52     private final int mIconDarkAlpha;
     53     private final int mIconDarkColor = 0xffffffff;
     54     protected final ViewInvertHelper mInvertHelper;
     55 
     56     protected final ViewTransformationHelper mTransformationHelper;
     57 
     58     protected int mColor;
     59     private ImageView mIcon;
     60 
     61     private ImageView mExpandButton;
     62     private NotificationHeaderView mNotificationHeader;
     63 
     64     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
     65         super(view, row);
     66         mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
     67         mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
     68         mTransformationHelper = new ViewTransformationHelper();
     69         resolveHeaderViews();
     70         updateInvertHelper();
     71     }
     72 
     73     protected void resolveHeaderViews() {
     74         mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
     75         mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
     76         mColor = resolveColor(mExpandButton);
     77         mNotificationHeader = (NotificationHeaderView) mView.findViewById(
     78                 com.android.internal.R.id.notification_header);
     79     }
     80 
     81     private int resolveColor(ImageView icon) {
     82         if (icon != null && icon.getDrawable() != null) {
     83             ColorFilter filter = icon.getDrawable().getColorFilter();
     84             if (filter instanceof PorterDuffColorFilter) {
     85                 return ((PorterDuffColorFilter) filter).getColor();
     86             }
     87         }
     88         return 0;
     89     }
     90 
     91     @Override
     92     public void notifyContentUpdated(StatusBarNotification notification) {
     93         super.notifyContentUpdated(notification);
     94 
     95         ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
     96 
     97         // Reinspect the notification.
     98         resolveHeaderViews();
     99         updateInvertHelper();
    100         updateTransformedTypes();
    101         addRemainingTransformTypes();
    102         updateCropToPaddingForImageViews();
    103 
    104         // We need to reset all views that are no longer transforming in case a view was previously
    105         // transformed, but now we decided to transform its container instead.
    106         ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews();
    107         for (int i = 0; i < previousViews.size(); i++) {
    108             View view = previousViews.valueAt(i);
    109             if (!currentViews.contains(view)) {
    110                 mTransformationHelper.resetTransformedView(view);
    111             }
    112         }
    113     }
    114 
    115     /**
    116      * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
    117      * child is faded automatically and doesn't have to be manually added.
    118      * The keys used for the views are the ids.
    119      */
    120     private void addRemainingTransformTypes() {
    121         mTransformationHelper.addRemainingTransformTypes(mView);
    122     }
    123 
    124     /**
    125      * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
    126      * anymore during these transitions. We can avoid that by using
    127      * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
    128      */
    129     private void updateCropToPaddingForImageViews() {
    130         Stack<View> stack = new Stack<>();
    131         stack.push(mView);
    132         while (!stack.isEmpty()) {
    133             View child = stack.pop();
    134             if (child instanceof ImageView) {
    135                 ((ImageView) child).setCropToPadding(true);
    136             } else if (child instanceof ViewGroup){
    137                 ViewGroup group = (ViewGroup) child;
    138                 for (int i = 0; i < group.getChildCount(); i++) {
    139                     stack.push(group.getChildAt(i));
    140                 }
    141             }
    142         }
    143     }
    144 
    145     protected void updateInvertHelper() {
    146         mInvertHelper.clearTargets();
    147         for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
    148             View child = mNotificationHeader.getChildAt(i);
    149             if (child != mIcon) {
    150                 mInvertHelper.addTarget(child);
    151             }
    152         }
    153     }
    154 
    155     protected void updateTransformedTypes() {
    156         mTransformationHelper.reset();
    157         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
    158                 mNotificationHeader);
    159     }
    160 
    161     @Override
    162     public void setDark(boolean dark, boolean fade, long delay) {
    163         if (dark == mDark && mDarkInitialized) {
    164             return;
    165         }
    166         super.setDark(dark, fade, delay);
    167         if (fade) {
    168             mInvertHelper.fade(dark, delay);
    169         } else {
    170             mInvertHelper.update(dark);
    171         }
    172         if (mIcon != null && !mRow.isChildInGroup()) {
    173             // We don't update the color for children views / their icon is invisible anyway.
    174             // It also may lead to bugs where the icon isn't correctly greyed out.
    175             boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
    176                     != NotificationHeaderView.NO_COLOR;
    177             if (fade) {
    178                 if (hadColorFilter) {
    179                     fadeIconColorFilter(mIcon, dark, delay);
    180                     fadeIconAlpha(mIcon, dark, delay);
    181                 } else {
    182                     fadeGrayscale(mIcon, dark, delay);
    183                 }
    184             } else {
    185                 if (hadColorFilter) {
    186                     updateIconColorFilter(mIcon, dark);
    187                     updateIconAlpha(mIcon, dark);
    188                 } else {
    189                     updateGrayscale(mIcon, dark);
    190                 }
    191             }
    192         }
    193     }
    194 
    195     private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
    196         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    197             @Override
    198             public void onAnimationUpdate(ValueAnimator animation) {
    199                 updateIconColorFilter(target, (Float) animation.getAnimatedValue());
    200             }
    201         }, dark, delay, null /* listener */);
    202     }
    203 
    204     private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
    205         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    206             @Override
    207             public void onAnimationUpdate(ValueAnimator animation) {
    208                 float t = (float) animation.getAnimatedValue();
    209                 target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
    210             }
    211         }, dark, delay, null /* listener */);
    212     }
    213 
    214     protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
    215         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    216             @Override
    217             public void onAnimationUpdate(ValueAnimator animation) {
    218                 updateGrayscaleMatrix((float) animation.getAnimatedValue());
    219                 target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
    220             }
    221         }, dark, delay, new AnimatorListenerAdapter() {
    222             @Override
    223             public void onAnimationEnd(Animator animation) {
    224                 if (!dark) {
    225                     target.setColorFilter(null);
    226                 }
    227             }
    228         });
    229     }
    230 
    231     private void updateIconColorFilter(ImageView target, boolean dark) {
    232         updateIconColorFilter(target, dark ? 1f : 0f);
    233     }
    234 
    235     private void updateIconColorFilter(ImageView target, float intensity) {
    236         int color = interpolateColor(mColor, mIconDarkColor, intensity);
    237         mIconColorFilter.setColor(color);
    238         Drawable iconDrawable = target.getDrawable();
    239 
    240         // Also, the notification might have been modified during the animation, so background
    241         // might be null here.
    242         if (iconDrawable != null) {
    243             Drawable d = iconDrawable.mutate();
    244             // DrawableContainer ignores the color filter if it's already set, so clear it first to
    245             // get it set and invalidated properly.
    246             d.setColorFilter(null);
    247             d.setColorFilter(mIconColorFilter);
    248         }
    249     }
    250 
    251     private void updateIconAlpha(ImageView target, boolean dark) {
    252         target.setImageAlpha(dark ? mIconDarkAlpha : 255);
    253     }
    254 
    255     protected void updateGrayscale(ImageView target, boolean dark) {
    256         if (dark) {
    257             updateGrayscaleMatrix(1f);
    258             target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
    259         } else {
    260             target.setColorFilter(null);
    261         }
    262     }
    263 
    264     @Override
    265     public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
    266         mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
    267         mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
    268     }
    269 
    270     private static int interpolateColor(int source, int target, float t) {
    271         int aSource = Color.alpha(source);
    272         int rSource = Color.red(source);
    273         int gSource = Color.green(source);
    274         int bSource = Color.blue(source);
    275         int aTarget = Color.alpha(target);
    276         int rTarget = Color.red(target);
    277         int gTarget = Color.green(target);
    278         int bTarget = Color.blue(target);
    279         return Color.argb(
    280                 (int) (aSource * (1f - t) + aTarget * t),
    281                 (int) (rSource * (1f - t) + rTarget * t),
    282                 (int) (gSource * (1f - t) + gTarget * t),
    283                 (int) (bSource * (1f - t) + bTarget * t));
    284     }
    285 
    286     @Override
    287     public NotificationHeaderView getNotificationHeader() {
    288         return mNotificationHeader;
    289     }
    290 
    291     @Override
    292     public TransformState getCurrentState(int fadingView) {
    293         return mTransformationHelper.getCurrentState(fadingView);
    294     }
    295 
    296     @Override
    297     public void transformTo(TransformableView notification, Runnable endRunnable) {
    298         mTransformationHelper.transformTo(notification, endRunnable);
    299     }
    300 
    301     @Override
    302     public void transformTo(TransformableView notification, float transformationAmount) {
    303         mTransformationHelper.transformTo(notification, transformationAmount);
    304     }
    305 
    306     @Override
    307     public void transformFrom(TransformableView notification) {
    308         mTransformationHelper.transformFrom(notification);
    309     }
    310 
    311     @Override
    312     public void transformFrom(TransformableView notification, float transformationAmount) {
    313         mTransformationHelper.transformFrom(notification, transformationAmount);
    314     }
    315 
    316     @Override
    317     public void setVisible(boolean visible) {
    318         super.setVisible(visible);
    319         mTransformationHelper.setVisible(visible);
    320     }
    321 }
    322