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