blob: 561fbb2b4aba0c63b8aaeea16b35e97865931ee0 [file] [log] [blame]
Joe Onorato0cbda992010-05-02 16:28:15 -07001/*
2 * Copyright (C) 2008 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
Joe Onorato79de0c52010-05-26 17:03:26 -040017package com.android.systemui.statusbar;
Joe Onorato0cbda992010-05-02 16:28:15 -070018
Selim Cinek49014f82016-11-04 14:55:30 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
Selim Cinek875ba9b2017-02-13 16:20:17 -080022import android.animation.ValueAnimator;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070023import android.app.Notification;
Joe Onorato0cbda992010-05-02 16:28:15 -070024import android.content.Context;
Adrian Roosa8e18ef2016-05-26 17:17:02 -070025import android.content.pm.ApplicationInfo;
Selim Cinek3e7592d2016-04-11 09:35:54 +080026import android.content.res.Configuration;
Joe Onorato0cbda992010-05-02 16:28:15 -070027import android.content.res.Resources;
Joe Onorato0cbda992010-05-02 16:28:15 -070028import android.graphics.Canvas;
Adrian Roos456e0052017-04-04 16:44:29 -070029import android.graphics.Color;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020030import android.graphics.ColorMatrix;
31import android.graphics.ColorMatrixColorFilter;
Joe Onorato6c01a112010-10-04 17:38:47 -040032import android.graphics.Paint;
33import android.graphics.Rect;
John Spurlockde84f0e2013-06-12 12:41:00 -040034import android.graphics.drawable.Drawable;
Dan Sandlerd63f9322015-05-06 15:18:49 -040035import android.graphics.drawable.Icon;
Adrian Roosa8e18ef2016-05-26 17:17:02 -070036import android.os.Parcelable;
Jeff Sharkeyded653b2012-09-27 19:09:24 -070037import android.os.UserHandle;
Selim Cinek72fc8db2017-06-06 18:07:47 -070038import android.service.notification.StatusBarNotification;
Lucas Dupin83519da2017-06-21 11:58:31 -070039import android.support.v4.graphics.ColorUtils;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070040import android.text.TextUtils;
Daniel Sandler05e24142011-11-10 11:56:49 -050041import android.util.AttributeSet;
Selim Cinek49014f82016-11-04 14:55:30 -070042import android.util.FloatProperty;
Joe Onoratof9ec03c2010-09-23 15:09:18 -070043import android.util.Log;
Selim Cinek49014f82016-11-04 14:55:30 -070044import android.util.Property;
Anthony Chen55e8e1e2016-01-08 10:31:46 -080045import android.util.TypedValue;
Joe Onorato0cbda992010-05-02 16:28:15 -070046import android.view.ViewDebug;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070047import android.view.accessibility.AccessibilityEvent;
Selim Cinek49014f82016-11-04 14:55:30 -070048import android.view.animation.Interpolator;
Winsonc0d70582016-01-29 10:24:39 -080049
Joe Onorato0cbda992010-05-02 16:28:15 -070050import com.android.internal.statusbar.StatusBarIcon;
Lucas Dupin83519da2017-06-21 11:58:31 -070051import com.android.internal.util.NotificationColorUtil;
Selim Cinek49014f82016-11-04 14:55:30 -070052import com.android.systemui.Interpolators;
Joe Onorato6c01a112010-10-04 17:38:47 -040053import com.android.systemui.R;
Adrian Roos456e0052017-04-04 16:44:29 -070054import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
Selim Cinek49014f82016-11-04 14:55:30 -070055import com.android.systemui.statusbar.notification.NotificationUtils;
Joe Onorato6c01a112010-10-04 17:38:47 -040056
John Spurlockde84f0e2013-06-12 12:41:00 -040057import java.text.NumberFormat;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020058import java.util.Arrays;
John Spurlockde84f0e2013-06-12 12:41:00 -040059
Joe Onorato0cbda992010-05-02 16:28:15 -070060public class StatusBarIconView extends AnimatedImageView {
Selim Cinek875ba9b2017-02-13 16:20:17 -080061 public static final int NO_COLOR = 0;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020062
63 /**
64 * Multiply alpha values with (1+DARK_ALPHA_BOOST) when dozing. The chosen value boosts
65 * everything above 30% to 50%, making it appear on 1bit color depths.
66 */
67 private static final float DARK_ALPHA_BOOST = 0.67f;
Selim Cinek875ba9b2017-02-13 16:20:17 -080068 private final int ANIMATION_DURATION_FAST = 100;
69
Selim Cinek49014f82016-11-04 14:55:30 -070070 public static final int STATE_ICON = 0;
71 public static final int STATE_DOT = 1;
72 public static final int STATE_HIDDEN = 2;
Joe Onorato0cbda992010-05-02 16:28:15 -070073
Selim Cinek49014f82016-11-04 14:55:30 -070074 private static final String TAG = "StatusBarIconView";
75 private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
76 = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
77
78 @Override
79 public void setValue(StatusBarIconView object, float value) {
80 object.setIconAppearAmount(value);
81 }
82
83 @Override
84 public Float get(StatusBarIconView object) {
85 return object.getIconAppearAmount();
86 }
87 };
Selim Cinek5b5beb012016-11-08 18:11:58 -080088 private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
Selim Cinek49014f82016-11-04 14:55:30 -070089 = new FloatProperty<StatusBarIconView>("dot_appear_amount") {
90
91 @Override
92 public void setValue(StatusBarIconView object, float value) {
93 object.setDotAppearAmount(value);
94 }
95
96 @Override
97 public Float get(StatusBarIconView object) {
98 return object.getDotAppearAmount();
99 }
100 };
101
102 private boolean mAlwaysScaleIcon;
Adrian Roos138f0342017-04-28 09:11:28 -0700103 private int mStatusBarIconDrawingSizeDark = 1;
104 private int mStatusBarIconDrawingSize = 1;
105 private int mStatusBarIconSize = 1;
Joe Onorato0cbda992010-05-02 16:28:15 -0700106 private StatusBarIcon mIcon;
107 @ViewDebug.ExportedProperty private String mSlot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400108 private Drawable mNumberBackground;
109 private Paint mNumberPain;
110 private int mNumberX;
111 private int mNumberY;
112 private String mNumberText;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700113 private StatusBarNotification mNotification;
Jason Monk3b230072015-05-29 11:11:29 -0400114 private final boolean mBlocked;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800115 private int mDensity;
Selim Cinek281c2022016-10-13 19:14:43 -0700116 private float mIconScale = 1.0f;
Selim Cinek49014f82016-11-04 14:55:30 -0700117 private final Paint mDotPaint = new Paint();
Selim Cinek49014f82016-11-04 14:55:30 -0700118 private float mDotRadius;
119 private int mStaticDotRadius;
120 private int mVisibleState = STATE_ICON;
121 private float mIconAppearAmount = 1.0f;
122 private ObjectAnimator mIconAppearAnimator;
123 private ObjectAnimator mDotAnimator;
124 private float mDotAppearAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800125 private OnVisibilityChangedListener mOnVisibilityChangedListener;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800126 private int mDrawableColor;
127 private int mIconColor;
Adrian Roos456e0052017-04-04 16:44:29 -0700128 private int mDecorColor;
129 private float mDarkAmount;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800130 private ValueAnimator mColorAnimator;
131 private int mCurrentSetColor = NO_COLOR;
132 private int mAnimationStartColor = NO_COLOR;
133 private final ValueAnimator.AnimatorUpdateListener mColorUpdater
134 = animation -> {
135 int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor,
136 animation.getAnimatedFraction());
137 setColorInternal(newColor);
138 };
Adrian Roos456e0052017-04-04 16:44:29 -0700139 private final NotificationIconDozeHelper mDozer;
Lucas Dupin83519da2017-06-21 11:58:31 -0700140 private int mContrastedDrawableColor;
141 private int mCachedContrastBackgroundColor = NO_COLOR;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200142 private float[] mMatrix;
143 private ColorMatrixColorFilter mMatrixColorFilter;
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700144 private boolean mIsInShelf;
Joe Onorato0cbda992010-05-02 16:28:15 -0700145
Selim Cinek72fc8db2017-06-06 18:07:47 -0700146 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
147 this(context, slot, sbn, false);
Jason Monk3b230072015-05-29 11:11:29 -0400148 }
149
Selim Cinek72fc8db2017-06-06 18:07:47 -0700150 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
Jason Monk3b230072015-05-29 11:11:29 -0400151 boolean blocked) {
Joe Onorato0cbda992010-05-02 16:28:15 -0700152 super(context);
Adrian Roos456e0052017-04-04 16:44:29 -0700153 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400154 mBlocked = blocked;
Joe Onorato0cbda992010-05-02 16:28:15 -0700155 mSlot = slot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400156 mNumberPain = new Paint();
157 mNumberPain.setTextAlign(Paint.Align.CENTER);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700158 mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
Joe Onorato6c01a112010-10-04 17:38:47 -0400159 mNumberPain.setAntiAlias(true);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700160 setNotification(sbn);
Adrian Roos138f0342017-04-28 09:11:28 -0700161 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800162 setScaleType(ScaleType.CENTER);
163 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Selim Cinek49014f82016-11-04 14:55:30 -0700164 if (mNotification != null) {
Selim Cinek875ba9b2017-02-13 16:20:17 -0800165 setDecorColor(getContext().getColor(
Selim Cinek49014f82016-11-04 14:55:30 -0700166 com.android.internal.R.color.notification_icon_default_color));
167 }
168 reloadDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800169 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400170
Adrian Roos138f0342017-04-28 09:11:28 -0700171 private void maybeUpdateIconScaleDimens() {
Daniel Sandler7579bca2011-08-18 15:47:26 -0400172 // We do not resize and scale system icons (on the right), only notification icons (on the
173 // left).
Selim Cinek3e7592d2016-04-11 09:35:54 +0800174 if (mNotification != null || mAlwaysScaleIcon) {
Adrian Roos138f0342017-04-28 09:11:28 -0700175 updateIconScaleDimens();
Daniel Sandler7579bca2011-08-18 15:47:26 -0400176 }
Selim Cinek3e7592d2016-04-11 09:35:54 +0800177 }
Daniel Sandlerabff0322011-09-27 11:19:34 -0400178
Adrian Roos138f0342017-04-28 09:11:28 -0700179 private void updateIconScaleDimens() {
Selim Cinek3e7592d2016-04-11 09:35:54 +0800180 Resources res = mContext.getResources();
Adrian Roos138f0342017-04-28 09:11:28 -0700181 mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
182 mStatusBarIconDrawingSizeDark =
183 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
184 mStatusBarIconDrawingSize =
185 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
186 updateIconScale();
187 }
188
189 private void updateIconScale() {
190 final float imageBounds = NotificationUtils.interpolate(
191 mStatusBarIconDrawingSize,
192 mStatusBarIconDrawingSizeDark,
193 mDarkAmount);
194 final int outerBounds = mStatusBarIconSize;
Selim Cinek281c2022016-10-13 19:14:43 -0700195 mIconScale = (float)imageBounds / (float)outerBounds;
Selim Cinek281c2022016-10-13 19:14:43 -0700196 }
197
Adrian Roosceac4a02017-05-30 20:25:52 -0700198 public float getIconScaleFullyDark() {
199 return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize;
200 }
201
Selim Cinek281c2022016-10-13 19:14:43 -0700202 public float getIconScale() {
203 return mIconScale;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800204 }
205
206 @Override
207 protected void onConfigurationChanged(Configuration newConfig) {
208 super.onConfigurationChanged(newConfig);
209 int density = newConfig.densityDpi;
210 if (density != mDensity) {
211 mDensity = density;
Adrian Roos138f0342017-04-28 09:11:28 -0700212 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800213 updateDrawable();
Selim Cinek49014f82016-11-04 14:55:30 -0700214 reloadDimens();
215 }
216 }
217
218 private void reloadDimens() {
219 boolean applyRadius = mDotRadius == mStaticDotRadius;
220 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
221 if (applyRadius) {
222 mDotRadius = mStaticDotRadius;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800223 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700224 }
225
Selim Cinek72fc8db2017-06-06 18:07:47 -0700226 public void setNotification(StatusBarNotification notification) {
Christoph Studera0506e72014-07-31 20:27:39 +0200227 mNotification = notification;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700228 if (notification != null) {
229 setContentDescription(notification.getNotification());
230 }
Christoph Studera0506e72014-07-31 20:27:39 +0200231 }
232
Daniel Sandler05e24142011-11-10 11:56:49 -0500233 public StatusBarIconView(Context context, AttributeSet attrs) {
234 super(context, attrs);
Adrian Roos456e0052017-04-04 16:44:29 -0700235 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400236 mBlocked = false;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800237 mAlwaysScaleIcon = true;
Adrian Roos138f0342017-04-28 09:11:28 -0700238 updateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800239 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Daniel Sandler05e24142011-11-10 11:56:49 -0500240 }
241
Joe Onorato0cbda992010-05-02 16:28:15 -0700242 private static boolean streq(String a, String b) {
Joe Onorato66d7d012010-05-14 10:05:10 -0700243 if (a == b) {
244 return true;
245 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700246 if (a == null && b != null) {
247 return false;
248 }
249 if (a != null && b == null) {
250 return false;
251 }
252 return a.equals(b);
253 }
254
Dan Sandlerd63f9322015-05-06 15:18:49 -0400255 public boolean equalIcons(Icon a, Icon b) {
256 if (a == b) return true;
257 if (a.getType() != b.getType()) return false;
258 switch (a.getType()) {
259 case Icon.TYPE_RESOURCE:
260 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
261 case Icon.TYPE_URI:
262 return a.getUriString().equals(b.getUriString());
263 default:
264 return false;
265 }
266 }
Joe Onorato005847b2010-06-04 16:08:02 -0400267 /**
268 * Returns whether the set succeeded.
269 */
270 public boolean set(StatusBarIcon icon) {
Dan Sandlerd63f9322015-05-06 15:18:49 -0400271 final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
Joe Onorato005847b2010-06-04 16:08:02 -0400272 final boolean levelEquals = iconEquals
273 && mIcon.iconLevel == icon.iconLevel;
274 final boolean visibilityEquals = mIcon != null
275 && mIcon.visible == icon.visible;
Joe Onorato6c01a112010-10-04 17:38:47 -0400276 final boolean numberEquals = mIcon != null
277 && mIcon.number == icon.number;
278 mIcon = icon.clone();
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700279 setContentDescription(icon.contentDescription);
Joe Onorato005847b2010-06-04 16:08:02 -0400280 if (!iconEquals) {
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800281 if (!updateDrawable(false /* no clear */)) return false;
Selim Cinek2b6eb8032016-12-29 14:22:21 +0100282 // we have to clear the grayscale tag since it may have changed
283 setTag(R.id.icon_is_grayscale, null);
Joe Onorato0cbda992010-05-02 16:28:15 -0700284 }
Joe Onorato005847b2010-06-04 16:08:02 -0400285 if (!levelEquals) {
286 setImageLevel(icon.iconLevel);
Joe Onorato0cbda992010-05-02 16:28:15 -0700287 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400288
Joe Onorato6c01a112010-10-04 17:38:47 -0400289 if (!numberEquals) {
John Spurlock01534782014-01-13 11:59:22 -0500290 if (icon.number > 0 && getContext().getResources().getBoolean(
Joe Onorato8595a3d2010-11-19 18:12:07 -0800291 R.bool.config_statusBarShowNumber)) {
Joe Onorato6c01a112010-10-04 17:38:47 -0400292 if (mNumberBackground == null) {
293 mNumberBackground = getContext().getResources().getDrawable(
294 R.drawable.ic_notification_overlay);
295 }
296 placeNumber();
297 } else {
298 mNumberBackground = null;
299 mNumberText = null;
300 }
301 invalidate();
302 }
Joe Onorato005847b2010-06-04 16:08:02 -0400303 if (!visibilityEquals) {
Jason Monk3b230072015-05-29 11:11:29 -0400304 setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
Joe Onorato005847b2010-06-04 16:08:02 -0400305 }
Joe Onorato005847b2010-06-04 16:08:02 -0400306 return true;
Joe Onorato0cbda992010-05-02 16:28:15 -0700307 }
308
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800309 public void updateDrawable() {
310 updateDrawable(true /* with clear */);
311 }
312
313 private boolean updateDrawable(boolean withClear) {
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100314 if (mIcon == null) {
315 return false;
316 }
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800317 Drawable drawable;
318 try {
319 drawable = getIcon(mIcon);
320 } catch (OutOfMemoryError e) {
321 Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
322 return false;
323 }
324
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800325 if (drawable == null) {
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800326 Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800327 return false;
328 }
329 if (withClear) {
330 setImageDrawable(null);
331 }
332 setImageDrawable(drawable);
333 return true;
334 }
335
Selim Cinek72fc8db2017-06-06 18:07:47 -0700336 public Icon getSourceIcon() {
337 return mIcon.icon;
338 }
339
Joe Onoratof5510542010-06-01 07:46:41 -0700340 private Drawable getIcon(StatusBarIcon icon) {
341 return getIcon(getContext(), icon);
342 }
343
Joe Onorato0cbda992010-05-02 16:28:15 -0700344 /**
Dan Sandlerd63f9322015-05-06 15:18:49 -0400345 * Returns the right icon to use for this item
John Spurlock209bede2013-07-17 12:23:27 -0400346 *
Dan Sandlerd63f9322015-05-06 15:18:49 -0400347 * @param context Context to use to get resources
Joe Onorato0cbda992010-05-02 16:28:15 -0700348 * @return Drawable for this item, or null if the package or item could not
349 * be found
350 */
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800351 public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
352 int userId = statusBarIcon.user.getIdentifier();
Dan Sandlerd63f9322015-05-06 15:18:49 -0400353 if (userId == UserHandle.USER_ALL) {
Xiaohui Chen87d0e252015-07-30 15:38:16 -0700354 userId = UserHandle.USER_SYSTEM;
Joe Onorato0cbda992010-05-02 16:28:15 -0700355 }
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800356
357 Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
358
359 TypedValue typedValue = new TypedValue();
360 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
361 float scaleFactor = typedValue.getFloat();
362
363 // No need to scale the icon, so return it as is.
364 if (scaleFactor == 1.f) {
365 return icon;
366 }
367
368 return new ScalingDrawableWrapper(icon, scaleFactor);
Joe Onorato0cbda992010-05-02 16:28:15 -0700369 }
Joe Onoratob77f53b2010-05-28 19:59:51 -0400370
371 public StatusBarIcon getStatusBarIcon() {
372 return mIcon;
373 }
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700374
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700375 @Override
376 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
377 super.onInitializeAccessibilityEvent(event);
378 if (mNotification != null) {
Selim Cinek72fc8db2017-06-06 18:07:47 -0700379 event.setParcelableData(mNotification.getNotification());
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700380 }
381 }
382
383 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400384 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
385 super.onSizeChanged(w, h, oldw, oldh);
386 if (mNumberBackground != null) {
387 placeNumber();
388 }
389 }
390
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700391 @Override
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100392 public void onRtlPropertiesChanged(int layoutDirection) {
393 super.onRtlPropertiesChanged(layoutDirection);
394 updateDrawable();
395 }
396
397 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400398 protected void onDraw(Canvas canvas) {
Selim Cinek49014f82016-11-04 14:55:30 -0700399 if (mIconAppearAmount > 0.0f) {
400 canvas.save();
401 canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
402 getWidth() / 2, getHeight() / 2);
403 super.onDraw(canvas);
404 canvas.restore();
405 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400406
407 if (mNumberBackground != null) {
408 mNumberBackground.draw(canvas);
409 canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
410 }
Selim Cinek49014f82016-11-04 14:55:30 -0700411 if (mDotAppearAmount != 0.0f) {
412 float radius;
413 float alpha;
414 if (mDotAppearAmount <= 1.0f) {
415 radius = mDotRadius * mDotAppearAmount;
416 alpha = 1.0f;
417 } else {
418 float fadeOutAmount = mDotAppearAmount - 1.0f;
419 alpha = 1.0f - fadeOutAmount;
420 radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
421 }
422 mDotPaint.setAlpha((int) (alpha * 255));
423 canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
424 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400425 }
426
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700427 @Override
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700428 protected void debug(int depth) {
429 super.debug(depth);
430 Log.d("View", debugIndent(depth) + "slot=" + mSlot);
431 Log.d("View", debugIndent(depth) + "icon=" + mIcon);
432 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400433
434 void placeNumber() {
Daniel Sandlerebce0112011-06-16 16:44:51 -0400435 final String str;
John Spurlock01534782014-01-13 11:59:22 -0500436 final int tooBig = getContext().getResources().getInteger(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400437 android.R.integer.status_bar_notification_info_maxnum);
438 if (mIcon.number > tooBig) {
John Spurlock01534782014-01-13 11:59:22 -0500439 str = getContext().getResources().getString(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400440 android.R.string.status_bar_notification_info_overflow);
441 } else {
442 NumberFormat f = NumberFormat.getIntegerInstance();
443 str = f.format(mIcon.number);
444 }
445 mNumberText = str;
446
Joe Onorato6c01a112010-10-04 17:38:47 -0400447 final int w = getWidth();
448 final int h = getHeight();
449 final Rect r = new Rect();
450 mNumberPain.getTextBounds(str, 0, str.length(), r);
451 final int tw = r.right - r.left;
452 final int th = r.bottom - r.top;
453 mNumberBackground.getPadding(r);
454 int dw = r.left + tw + r.right;
455 if (dw < mNumberBackground.getMinimumWidth()) {
456 dw = mNumberBackground.getMinimumWidth();
457 }
458 mNumberX = w-r.right-((dw-r.right-r.left)/2);
459 int dh = r.top + th + r.bottom;
460 if (dh < mNumberBackground.getMinimumWidth()) {
461 dh = mNumberBackground.getMinimumWidth();
462 }
463 mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
464 mNumberBackground.setBounds(w-dw, h-dh, w, h);
465 }
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700466
467 private void setContentDescription(Notification notification) {
468 if (notification != null) {
Adrian Rooseba05822016-04-22 17:09:27 -0700469 String d = contentDescForNotification(mContext, notification);
470 if (!TextUtils.isEmpty(d)) {
471 setContentDescription(d);
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700472 }
473 }
474 }
Daniel Sandler7579bca2011-08-18 15:47:26 -0400475
476 public String toString() {
John Spurlock209bede2013-07-17 12:23:27 -0400477 return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
Daniel Sandler7579bca2011-08-18 15:47:26 -0400478 + " notification=" + mNotification + ")";
479 }
Jason Monk3b230072015-05-29 11:11:29 -0400480
Selim Cinek72fc8db2017-06-06 18:07:47 -0700481 public StatusBarNotification getNotification() {
482 return mNotification;
483 }
484
Jason Monk3b230072015-05-29 11:11:29 -0400485 public String getSlot() {
486 return mSlot;
487 }
Adrian Rooseba05822016-04-22 17:09:27 -0700488
489
490 public static String contentDescForNotification(Context c, Notification n) {
Adrian Roosa8e18ef2016-05-26 17:17:02 -0700491 String appName = "";
492 try {
493 Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
494 appName = builder.loadHeaderAppName();
495 } catch (RuntimeException e) {
496 Log.e(TAG, "Unable to recover builder", e);
497 // Trying to get the app name from the app info instead.
498 Parcelable appInfo = n.extras.getParcelable(
499 Notification.EXTRA_BUILDER_APPLICATION_INFO);
500 if (appInfo instanceof ApplicationInfo) {
501 appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel(
502 c.getPackageManager()));
503 }
504 }
Adrian Roos51548e62016-04-28 11:20:26 -0700505
Julia Reynolds7f9ce782016-05-17 15:34:34 -0400506 CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
Adrian Roosf76600e2017-04-19 15:32:43 -0700507 CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
Adrian Rooseba05822016-04-22 17:09:27 -0700508 CharSequence ticker = n.tickerText;
Adrian Roos51548e62016-04-28 11:20:26 -0700509
Adrian Roosf76600e2017-04-19 15:32:43 -0700510 // Some apps just put the app name into the title
511 CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
512
513 CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
Adrian Roos6e782a52017-03-30 18:34:10 -0700514 : !TextUtils.isEmpty(ticker) ? ticker : "";
Adrian Roos51548e62016-04-28 11:20:26 -0700515
516 return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
Adrian Rooseba05822016-04-22 17:09:27 -0700517 }
Adrian Roos51548e62016-04-28 11:20:26 -0700518
Selim Cinek875ba9b2017-02-13 16:20:17 -0800519 /**
520 * Set the color that is used to draw decoration like the overflow dot. This will not be applied
521 * to the drawable.
522 */
523 public void setDecorColor(int iconTint) {
Adrian Roos456e0052017-04-04 16:44:29 -0700524 mDecorColor = iconTint;
525 updateDecorColor();
526 }
527
528 private void updateDecorColor() {
529 int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
530 if (mDotPaint.getColor() != color) {
531 mDotPaint.setColor(color);
532
533 if (mDotAppearAmount != 0) {
534 invalidate();
535 }
536 }
Selim Cinek49014f82016-11-04 14:55:30 -0700537 }
538
Selim Cinek875ba9b2017-02-13 16:20:17 -0800539 /**
540 * Set the static color that should be used for the drawable of this icon if it's not
541 * transitioning this also immediately sets the color.
542 */
543 public void setStaticDrawableColor(int color) {
544 mDrawableColor = color;
545 setColorInternal(color);
Lucas Dupin83519da2017-06-21 11:58:31 -0700546 updateContrastedStaticColor();
Selim Cinek875ba9b2017-02-13 16:20:17 -0800547 mIconColor = color;
Adrian Roos456e0052017-04-04 16:44:29 -0700548 mDozer.setColor(color);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800549 }
550
551 private void setColorInternal(int color) {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700552 mCurrentSetColor = color;
553 updateIconColor();
554 }
555
556 private void updateIconColor() {
557 if (mCurrentSetColor != NO_COLOR) {
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200558 if (mMatrixColorFilter == null) {
559 mMatrix = new float[4 * 5];
560 mMatrixColorFilter = new ColorMatrixColorFilter(mMatrix);
561 }
562 int color = NotificationUtils.interpolateColors(
563 mCurrentSetColor, Color.WHITE, mDarkAmount);
564 updateTintMatrix(mMatrix, color, DARK_ALPHA_BOOST * mDarkAmount);
565 mMatrixColorFilter.setColorMatrixArray(mMatrix);
566 setColorFilter(mMatrixColorFilter);
567 invalidate(); // setColorFilter only invalidates if the filter instance changed.
Selim Cinek875ba9b2017-02-13 16:20:17 -0800568 } else {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700569 mDozer.updateGrayscale(this, mDarkAmount);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800570 }
Selim Cinek875ba9b2017-02-13 16:20:17 -0800571 }
572
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200573 /**
574 * Updates {@param array} such that it represents a matrix that changes RGB to {@param color}
Adrian Roos24dbff32017-08-02 16:57:11 +0200575 * and multiplies the alpha channel with the color's alpha+{@param alphaBoost}.
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200576 */
577 private static void updateTintMatrix(float[] array, int color, float alphaBoost) {
578 Arrays.fill(array, 0);
579 array[4] = Color.red(color);
580 array[9] = Color.green(color);
581 array[14] = Color.blue(color);
Adrian Roos24dbff32017-08-02 16:57:11 +0200582 array[18] = Color.alpha(color) / 255f + alphaBoost;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200583 }
584
Selim Cinek875ba9b2017-02-13 16:20:17 -0800585 public void setIconColor(int iconColor, boolean animate) {
586 if (mIconColor != iconColor) {
587 mIconColor = iconColor;
588 if (mColorAnimator != null) {
589 mColorAnimator.cancel();
590 }
591 if (mCurrentSetColor == iconColor) {
592 return;
593 }
594 if (animate && mCurrentSetColor != NO_COLOR) {
595 mAnimationStartColor = mCurrentSetColor;
596 mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
597 mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
598 mColorAnimator.setDuration(ANIMATION_DURATION_FAST);
599 mColorAnimator.addUpdateListener(mColorUpdater);
600 mColorAnimator.addListener(new AnimatorListenerAdapter() {
601 @Override
602 public void onAnimationEnd(Animator animation) {
603 mColorAnimator = null;
604 mAnimationStartColor = NO_COLOR;
605 }
606 });
607 mColorAnimator.start();
608 } else {
609 setColorInternal(iconColor);
610 }
611 }
612 }
613
614 public int getStaticDrawableColor() {
615 return mDrawableColor;
616 }
617
Lucas Dupin83519da2017-06-21 11:58:31 -0700618 /**
619 * A drawable color that passes GAR on a specific background.
620 * This value is cached.
621 *
622 * @param backgroundColor Background to test against.
623 * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}.
624 */
625 int getContrastedStaticDrawableColor(int backgroundColor) {
626 if (mCachedContrastBackgroundColor != backgroundColor) {
627 mCachedContrastBackgroundColor = backgroundColor;
628 updateContrastedStaticColor();
629 }
630 return mContrastedDrawableColor;
631 }
632
633 private void updateContrastedStaticColor() {
Lucas Dupinddbaf842017-06-23 09:30:49 -0700634 if (Color.alpha(mCachedContrastBackgroundColor) != 255) {
635 mContrastedDrawableColor = mDrawableColor;
Lucas Dupin83519da2017-06-21 11:58:31 -0700636 return;
637 }
638 // We'll modify the color if it doesn't pass GAR
639 int contrastedColor = mDrawableColor;
640 if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor,
641 contrastedColor)) {
642 float[] hsl = new float[3];
643 ColorUtils.colorToHSL(mDrawableColor, hsl);
644 // This is basically a light grey, pushing the color will only distort it.
645 // Best thing to do in here is to fallback to the default color.
646 if (hsl[1] < 0.2f) {
647 contrastedColor = Notification.COLOR_DEFAULT;
648 }
649 contrastedColor = NotificationColorUtil.resolveContrastColor(mContext,
650 contrastedColor, mCachedContrastBackgroundColor);
651 }
652 mContrastedDrawableColor = contrastedColor;
653 }
654
Selim Cinek5b5beb012016-11-08 18:11:58 -0800655 public void setVisibleState(int state) {
656 setVisibleState(state, true /* animate */, null /* endRunnable */);
657 }
658
659 public void setVisibleState(int state, boolean animate) {
660 setVisibleState(state, animate, null);
661 }
662
663 @Override
664 public boolean hasOverlappingRendering() {
665 return false;
666 }
667
668 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800669 boolean runnableAdded = false;
Selim Cinek49014f82016-11-04 14:55:30 -0700670 if (visibleState != mVisibleState) {
671 mVisibleState = visibleState;
Selim Cinek5ece5a72017-02-15 11:47:42 -0800672 if (mIconAppearAnimator != null) {
673 mIconAppearAnimator.cancel();
674 }
675 if (mDotAnimator != null) {
676 mDotAnimator.cancel();
677 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800678 if (animate) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800679 float targetAmount = 0.0f;
680 Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
681 if (visibleState == STATE_ICON) {
682 targetAmount = 1.0f;
683 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
684 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800685 float currentAmount = getIconAppearAmount();
686 if (targetAmount != currentAmount) {
687 mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
688 currentAmount, targetAmount);
689 mIconAppearAnimator.setInterpolator(interpolator);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800690 mIconAppearAnimator.setDuration(ANIMATION_DURATION_FAST);
Selim Cinek65d418e2016-11-29 15:42:34 -0800691 mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
692 @Override
693 public void onAnimationEnd(Animator animation) {
694 mIconAppearAnimator = null;
695 runRunnable(endRunnable);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800696 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800697 });
698 mIconAppearAnimator.start();
699 runnableAdded = true;
700 }
Selim Cinek49014f82016-11-04 14:55:30 -0700701
Selim Cinek5b5beb012016-11-08 18:11:58 -0800702 targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
703 interpolator = Interpolators.FAST_OUT_LINEAR_IN;
704 if (visibleState == STATE_DOT) {
705 targetAmount = 1.0f;
706 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
707 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800708 currentAmount = getDotAppearAmount();
709 if (targetAmount != currentAmount) {
710 mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
711 currentAmount, targetAmount);
712 mDotAnimator.setInterpolator(interpolator);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800713 mDotAnimator.setDuration(ANIMATION_DURATION_FAST);
Selim Cinek65d418e2016-11-29 15:42:34 -0800714 final boolean runRunnable = !runnableAdded;
715 mDotAnimator.addListener(new AnimatorListenerAdapter() {
716 @Override
717 public void onAnimationEnd(Animator animation) {
718 mDotAnimator = null;
719 if (runRunnable) {
720 runRunnable(endRunnable);
721 }
722 }
723 });
724 mDotAnimator.start();
725 runnableAdded = true;
726 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800727 } else {
728 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
Selim Cinek01a73f92016-12-06 16:13:42 -0800729 setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
730 : visibleState == STATE_ICON ? 2.0f
731 : 0.0f);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800732 }
Selim Cinek49014f82016-11-04 14:55:30 -0700733 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800734 if (!runnableAdded) {
735 runRunnable(endRunnable);
736 }
737 }
738
739 private void runRunnable(Runnable runnable) {
740 if (runnable != null) {
741 runnable.run();
742 }
Selim Cinek49014f82016-11-04 14:55:30 -0700743 }
744
Selim Cinek5b5beb012016-11-08 18:11:58 -0800745 public void setIconAppearAmount(float iconAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800746 if (mIconAppearAmount != iconAppearAmount) {
747 mIconAppearAmount = iconAppearAmount;
748 invalidate();
749 }
Selim Cinek49014f82016-11-04 14:55:30 -0700750 }
751
752 public float getIconAppearAmount() {
753 return mIconAppearAmount;
754 }
755
756 public int getVisibleState() {
757 return mVisibleState;
758 }
759
760 public void setDotAppearAmount(float dotAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800761 if (mDotAppearAmount != dotAppearAmount) {
762 mDotAppearAmount = dotAppearAmount;
763 invalidate();
764 }
Selim Cinek49014f82016-11-04 14:55:30 -0700765 }
766
Selim Cinek2b549f42016-11-22 16:38:51 -0800767 @Override
768 public void setVisibility(int visibility) {
769 super.setVisibility(visibility);
770 if (mOnVisibilityChangedListener != null) {
771 mOnVisibilityChangedListener.onVisibilityChanged(visibility);
772 }
773 }
774
Selim Cinek49014f82016-11-04 14:55:30 -0700775 public float getDotAppearAmount() {
776 return mDotAppearAmount;
777 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800778
779 public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
780 mOnVisibilityChangedListener = listener;
781 }
782
Adrian Roos456e0052017-04-04 16:44:29 -0700783 public void setDark(boolean dark, boolean fade, long delay) {
Adrian Roos456e0052017-04-04 16:44:29 -0700784 mDozer.setIntensityDark(f -> {
785 mDarkAmount = f;
Adrian Roos138f0342017-04-28 09:11:28 -0700786 updateIconScale();
Adrian Roos456e0052017-04-04 16:44:29 -0700787 updateDecorColor();
Adrian Roosf3eacd32017-04-14 16:35:58 -0700788 updateIconColor();
Adrian Roosfe0071f2017-07-06 18:45:10 +0200789 updateAllowAnimation();
Adrian Roos456e0052017-04-04 16:44:29 -0700790 }, dark, fade, delay);
791 }
792
Adrian Roosfe0071f2017-07-06 18:45:10 +0200793 private void updateAllowAnimation() {
794 if (mDarkAmount == 0 || mDarkAmount == 1) {
795 setAllowAnimation(mDarkAmount == 0);
796 }
797 }
798
Selim Cinek13859052017-10-05 17:12:18 -0700799 /**
800 * This method returns the drawing rect for the view which is different from the regular
801 * drawing rect, since we layout all children at position 0 and usually the translation is
802 * neglected. The standard implementation doesn't account for translation.
803 *
804 * @param outRect The (scrolled) drawing bounds of the view.
805 */
806 @Override
807 public void getDrawingRect(Rect outRect) {
808 super.getDrawingRect(outRect);
809 float translationX = getTranslationX();
810 float translationY = getTranslationY();
811 outRect.left += translationX;
812 outRect.right += translationX;
813 outRect.top += translationY;
814 outRect.bottom += translationY;
815 }
816
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700817 public void setIsInShelf(boolean isInShelf) {
818 mIsInShelf = isInShelf;
819 }
820
821 public boolean isInShelf() {
822 return mIsInShelf;
823 }
824
Selim Cinek2b549f42016-11-22 16:38:51 -0800825 public interface OnVisibilityChangedListener {
826 void onVisibilityChanged(int newVisibility);
827 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700828}