Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2014 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;
     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.ColorMatrix;
     26 import android.graphics.ColorMatrixColorFilter;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.PorterDuffColorFilter;
     29 import android.graphics.drawable.Drawable;
     30 import android.view.View;
     31 import android.view.animation.AnimationUtils;
     32 import android.view.animation.Interpolator;
     33 import android.widget.ImageView;
     34 
     35 import com.android.systemui.R;
     36 import com.android.systemui.ViewInvertHelper;
     37 import com.android.systemui.statusbar.phone.NotificationPanelView;
     38 
     39 /**
     40  * Wraps a notification view inflated from a template.
     41  */
     42 public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
     43 
     44     private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
     45     private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
     46             0, PorterDuff.Mode.SRC_ATOP);
     47     private final int mIconDarkAlpha;
     48     private final int mIconBackgroundDarkColor;
     49     private final Interpolator mLinearOutSlowInInterpolator;
     50 
     51     private int mIconBackgroundColor;
     52     private ViewInvertHelper mInvertHelper;
     53     private ImageView mIcon;
     54     protected ImageView mPicture;
     55 
     56     /** Whether the icon needs to be forced grayscale when in dark mode. */
     57     private boolean mIconForceGraysaleWhenDark;
     58 
     59     protected NotificationTemplateViewWrapper(Context ctx, View view) {
     60         super(view);
     61         mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
     62         mIconBackgroundDarkColor =
     63                 ctx.getResources().getColor(R.color.doze_small_icon_background_color);
     64         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
     65                 android.R.interpolator.linear_out_slow_in);
     66         resolveViews();
     67     }
     68 
     69     private void resolveViews() {
     70         View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
     71         mInvertHelper = mainColumn != null
     72                 ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
     73                 : null;
     74         ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
     75         ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
     76         mIcon = resolveIcon(largeIcon, rightIcon);
     77         mPicture = resolvePicture(largeIcon);
     78         mIconBackgroundColor = resolveBackgroundColor(mIcon);
     79 
     80         // If the icon already has a color filter, we assume that we already forced the icon to be
     81         // white when we created the notification.
     82         final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
     83         mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
     84     }
     85 
     86     private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
     87         return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
     88                 : rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
     89                 : null;
     90     }
     91 
     92     private ImageView resolvePicture(ImageView largeIcon) {
     93         return largeIcon != null && largeIcon.getBackground() == null
     94                 ? largeIcon
     95                 : null;
     96     }
     97 
     98     private int resolveBackgroundColor(ImageView icon) {
     99         if (icon != null && icon.getBackground() != null) {
    100             ColorFilter filter = icon.getBackground().getColorFilter();
    101             if (filter instanceof PorterDuffColorFilter) {
    102                 return ((PorterDuffColorFilter) filter).getColor();
    103             }
    104         }
    105         return 0;
    106     }
    107 
    108     @Override
    109     public void notifyContentUpdated() {
    110         super.notifyContentUpdated();
    111 
    112         // Reinspect the notification.
    113         resolveViews();
    114     }
    115 
    116     @Override
    117     public void setDark(boolean dark, boolean fade, long delay) {
    118         if (mInvertHelper != null) {
    119             if (fade) {
    120                 mInvertHelper.fade(dark, delay);
    121             } else {
    122                 mInvertHelper.update(dark);
    123             }
    124         }
    125         if (mIcon != null) {
    126             if (fade) {
    127                 fadeIconColorFilter(mIcon, dark, delay);
    128                 fadeIconAlpha(mIcon, dark, delay);
    129                 if (!mIconForceGraysaleWhenDark) {
    130                     fadeGrayscale(mIcon, dark, delay);
    131                 }
    132             } else {
    133                 updateIconColorFilter(mIcon, dark);
    134                 updateIconAlpha(mIcon, dark);
    135                 if (!mIconForceGraysaleWhenDark) {
    136                     updateGrayscale(mIcon, dark);
    137                 }
    138             }
    139         }
    140         setPictureGrayscale(dark, fade, delay);
    141     }
    142 
    143     protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
    144         if (mPicture != null) {
    145             if (fade) {
    146                 fadeGrayscale(mPicture, grayscale, delay);
    147             } else {
    148                 updateGrayscale(mPicture, grayscale);
    149             }
    150         }
    151     }
    152 
    153     private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
    154             boolean dark, long delay, Animator.AnimatorListener listener) {
    155         float startIntensity = dark ? 0f : 1f;
    156         float endIntensity = dark ? 1f : 0f;
    157         ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
    158         animator.addUpdateListener(updateListener);
    159         animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
    160         animator.setInterpolator(mLinearOutSlowInInterpolator);
    161         animator.setStartDelay(delay);
    162         if (listener != null) {
    163             animator.addListener(listener);
    164         }
    165         animator.start();
    166     }
    167 
    168     private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
    169         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    170             @Override
    171             public void onAnimationUpdate(ValueAnimator animation) {
    172                 updateIconColorFilter(target, (Float) animation.getAnimatedValue());
    173             }
    174         }, dark, delay, null /* listener */);
    175     }
    176 
    177     private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
    178         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    179             @Override
    180             public void onAnimationUpdate(ValueAnimator animation) {
    181                 float t = (float) animation.getAnimatedValue();
    182                 target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
    183             }
    184         }, dark, delay, null /* listener */);
    185     }
    186 
    187     protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
    188         startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
    189             @Override
    190             public void onAnimationUpdate(ValueAnimator animation) {
    191                 updateGrayscaleMatrix((float) animation.getAnimatedValue());
    192                 target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
    193             }
    194         }, dark, delay, new AnimatorListenerAdapter() {
    195             @Override
    196             public void onAnimationEnd(Animator animation) {
    197                 if (!dark) {
    198                     target.setColorFilter(null);
    199                 }
    200             }
    201         });
    202     }
    203 
    204     private void updateIconColorFilter(ImageView target, boolean dark) {
    205         updateIconColorFilter(target, dark ? 1f : 0f);
    206     }
    207 
    208     private void updateIconColorFilter(ImageView target, float intensity) {
    209         int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
    210         mIconColorFilter.setColor(color);
    211         Drawable background = target.getBackground();
    212 
    213         // The background might be null for legacy notifications. Also, the notification might have
    214         // been modified during the animation, so background might be null here.
    215         if (background != null) {
    216             background.mutate().setColorFilter(mIconColorFilter);
    217         }
    218     }
    219 
    220     private void updateIconAlpha(ImageView target, boolean dark) {
    221         target.setImageAlpha(dark ? mIconDarkAlpha : 255);
    222     }
    223 
    224     protected void updateGrayscale(ImageView target, boolean dark) {
    225         if (dark) {
    226             updateGrayscaleMatrix(1f);
    227             target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
    228         } else {
    229             target.setColorFilter(null);
    230         }
    231     }
    232 
    233     private void updateGrayscaleMatrix(float intensity) {
    234         mGrayscaleColorMatrix.setSaturation(1 - intensity);
    235     }
    236 
    237     private static int interpolateColor(int source, int target, float t) {
    238         int aSource = Color.alpha(source);
    239         int rSource = Color.red(source);
    240         int gSource = Color.green(source);
    241         int bSource = Color.blue(source);
    242         int aTarget = Color.alpha(target);
    243         int rTarget = Color.red(target);
    244         int gTarget = Color.green(target);
    245         int bTarget = Color.blue(target);
    246         return Color.argb(
    247                 (int) (aSource * (1f - t) + aTarget * t),
    248                 (int) (rSource * (1f - t) + rTarget * t),
    249                 (int) (gSource * (1f - t) + gTarget * t),
    250                 (int) (bSource * (1f - t) + bTarget * t));
    251     }
    252 }
    253