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 
NotificationTemplateViewWrapper(Context ctx, View view)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.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 
resolveViews()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 
resolveIcon(ImageView largeIcon, ImageView rightIcon)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 
resolvePicture(ImageView largeIcon)92     private ImageView resolvePicture(ImageView largeIcon) {
93         return largeIcon != null && largeIcon.getBackground() == null
94                 ? largeIcon
95                 : null;
96     }
97 
resolveBackgroundColor(ImageView icon)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
notifyContentUpdated()109     public void notifyContentUpdated() {
110         super.notifyContentUpdated();
111 
112         // Reinspect the notification.
113         resolveViews();
114     }
115 
116     @Override
setDark(boolean dark, boolean fade, long delay)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 
setPictureGrayscale(boolean grayscale, boolean fade, long delay)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 
startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, boolean dark, long delay, Animator.AnimatorListener listener)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 
fadeIconColorFilter(final ImageView target, boolean dark, long delay)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 
fadeIconAlpha(final ImageView target, boolean dark, long delay)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 
fadeGrayscale(final ImageView target, final boolean dark, long delay)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 
updateIconColorFilter(ImageView target, boolean dark)204     private void updateIconColorFilter(ImageView target, boolean dark) {
205         updateIconColorFilter(target, dark ? 1f : 0f);
206     }
207 
updateIconColorFilter(ImageView target, float intensity)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 
updateIconAlpha(ImageView target, boolean dark)220     private void updateIconAlpha(ImageView target, boolean dark) {
221         target.setImageAlpha(dark ? mIconDarkAlpha : 255);
222     }
223 
updateGrayscale(ImageView target, boolean dark)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 
updateGrayscaleMatrix(float intensity)233     private void updateGrayscaleMatrix(float intensity) {
234         mGrayscaleColorMatrix.setSaturation(1 - intensity);
235     }
236 
interpolateColor(int source, int target, float t)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