blob: e729f605e948e0a063d5fdc395cd82c1361fc526 [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 Cinek875ba9b2017-02-13 16:20:17 -080026import android.content.res.ColorStateList;
Selim Cinek3e7592d2016-04-11 09:35:54 +080027import android.content.res.Configuration;
Joe Onorato0cbda992010-05-02 16:28:15 -070028import android.content.res.Resources;
Joe Onorato0cbda992010-05-02 16:28:15 -070029import android.graphics.Canvas;
Adrian Roos456e0052017-04-04 16:44:29 -070030import android.graphics.Color;
Joe Onorato6c01a112010-10-04 17:38:47 -040031import android.graphics.Paint;
32import android.graphics.Rect;
John Spurlockde84f0e2013-06-12 12:41:00 -040033import android.graphics.drawable.Drawable;
Dan Sandlerd63f9322015-05-06 15:18:49 -040034import android.graphics.drawable.Icon;
Adrian Roosa8e18ef2016-05-26 17:17:02 -070035import android.os.Parcelable;
Jeff Sharkeyded653b2012-09-27 19:09:24 -070036import android.os.UserHandle;
Selim Cinek72fc8db2017-06-06 18:07:47 -070037import android.service.notification.StatusBarNotification;
Lucas Dupin83519da2017-06-21 11:58:31 -070038import android.support.v4.graphics.ColorUtils;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070039import android.text.TextUtils;
Daniel Sandler05e24142011-11-10 11:56:49 -050040import android.util.AttributeSet;
Selim Cinek49014f82016-11-04 14:55:30 -070041import android.util.FloatProperty;
Joe Onoratof9ec03c2010-09-23 15:09:18 -070042import android.util.Log;
Selim Cinek49014f82016-11-04 14:55:30 -070043import android.util.Property;
Anthony Chen55e8e1e2016-01-08 10:31:46 -080044import android.util.TypedValue;
Joe Onorato0cbda992010-05-02 16:28:15 -070045import android.view.ViewDebug;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070046import android.view.accessibility.AccessibilityEvent;
Selim Cinek49014f82016-11-04 14:55:30 -070047import android.view.animation.Interpolator;
Winsonc0d70582016-01-29 10:24:39 -080048
Joe Onorato0cbda992010-05-02 16:28:15 -070049import com.android.internal.statusbar.StatusBarIcon;
Lucas Dupin83519da2017-06-21 11:58:31 -070050import com.android.internal.util.NotificationColorUtil;
Selim Cinek49014f82016-11-04 14:55:30 -070051import com.android.systemui.Interpolators;
Joe Onorato6c01a112010-10-04 17:38:47 -040052import com.android.systemui.R;
Adrian Roos456e0052017-04-04 16:44:29 -070053import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
Selim Cinek49014f82016-11-04 14:55:30 -070054import com.android.systemui.statusbar.notification.NotificationUtils;
Joe Onorato6c01a112010-10-04 17:38:47 -040055
John Spurlockde84f0e2013-06-12 12:41:00 -040056import java.text.NumberFormat;
57
Joe Onorato0cbda992010-05-02 16:28:15 -070058public class StatusBarIconView extends AnimatedImageView {
Selim Cinek875ba9b2017-02-13 16:20:17 -080059 public static final int NO_COLOR = 0;
60 private final int ANIMATION_DURATION_FAST = 100;
61
Selim Cinek49014f82016-11-04 14:55:30 -070062 public static final int STATE_ICON = 0;
63 public static final int STATE_DOT = 1;
64 public static final int STATE_HIDDEN = 2;
Joe Onorato0cbda992010-05-02 16:28:15 -070065
Selim Cinek49014f82016-11-04 14:55:30 -070066 private static final String TAG = "StatusBarIconView";
67 private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
68 = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
69
70 @Override
71 public void setValue(StatusBarIconView object, float value) {
72 object.setIconAppearAmount(value);
73 }
74
75 @Override
76 public Float get(StatusBarIconView object) {
77 return object.getIconAppearAmount();
78 }
79 };
Selim Cinek5b5beb012016-11-08 18:11:58 -080080 private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
Selim Cinek49014f82016-11-04 14:55:30 -070081 = new FloatProperty<StatusBarIconView>("dot_appear_amount") {
82
83 @Override
84 public void setValue(StatusBarIconView object, float value) {
85 object.setDotAppearAmount(value);
86 }
87
88 @Override
89 public Float get(StatusBarIconView object) {
90 return object.getDotAppearAmount();
91 }
92 };
93
94 private boolean mAlwaysScaleIcon;
Adrian Roos138f0342017-04-28 09:11:28 -070095 private int mStatusBarIconDrawingSizeDark = 1;
96 private int mStatusBarIconDrawingSize = 1;
97 private int mStatusBarIconSize = 1;
Joe Onorato0cbda992010-05-02 16:28:15 -070098 private StatusBarIcon mIcon;
99 @ViewDebug.ExportedProperty private String mSlot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400100 private Drawable mNumberBackground;
101 private Paint mNumberPain;
102 private int mNumberX;
103 private int mNumberY;
104 private String mNumberText;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700105 private StatusBarNotification mNotification;
Jason Monk3b230072015-05-29 11:11:29 -0400106 private final boolean mBlocked;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800107 private int mDensity;
Selim Cinek281c2022016-10-13 19:14:43 -0700108 private float mIconScale = 1.0f;
Selim Cinek49014f82016-11-04 14:55:30 -0700109 private final Paint mDotPaint = new Paint();
Selim Cinek49014f82016-11-04 14:55:30 -0700110 private float mDotRadius;
111 private int mStaticDotRadius;
112 private int mVisibleState = STATE_ICON;
113 private float mIconAppearAmount = 1.0f;
114 private ObjectAnimator mIconAppearAnimator;
115 private ObjectAnimator mDotAnimator;
116 private float mDotAppearAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800117 private OnVisibilityChangedListener mOnVisibilityChangedListener;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800118 private int mDrawableColor;
119 private int mIconColor;
Adrian Roos456e0052017-04-04 16:44:29 -0700120 private int mDecorColor;
121 private float mDarkAmount;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800122 private ValueAnimator mColorAnimator;
123 private int mCurrentSetColor = NO_COLOR;
124 private int mAnimationStartColor = NO_COLOR;
125 private final ValueAnimator.AnimatorUpdateListener mColorUpdater
126 = animation -> {
127 int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor,
128 animation.getAnimatedFraction());
129 setColorInternal(newColor);
130 };
Adrian Roos456e0052017-04-04 16:44:29 -0700131 private final NotificationIconDozeHelper mDozer;
Lucas Dupin83519da2017-06-21 11:58:31 -0700132 private int mContrastedDrawableColor;
133 private int mCachedContrastBackgroundColor = NO_COLOR;
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700134 private boolean mIsInShelf;
Joe Onorato0cbda992010-05-02 16:28:15 -0700135
Selim Cinek72fc8db2017-06-06 18:07:47 -0700136 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
137 this(context, slot, sbn, false);
Jason Monk3b230072015-05-29 11:11:29 -0400138 }
139
Selim Cinek72fc8db2017-06-06 18:07:47 -0700140 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
Jason Monk3b230072015-05-29 11:11:29 -0400141 boolean blocked) {
Joe Onorato0cbda992010-05-02 16:28:15 -0700142 super(context);
Adrian Roos456e0052017-04-04 16:44:29 -0700143 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400144 mBlocked = blocked;
Joe Onorato0cbda992010-05-02 16:28:15 -0700145 mSlot = slot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400146 mNumberPain = new Paint();
147 mNumberPain.setTextAlign(Paint.Align.CENTER);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700148 mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
Joe Onorato6c01a112010-10-04 17:38:47 -0400149 mNumberPain.setAntiAlias(true);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700150 setNotification(sbn);
Adrian Roos138f0342017-04-28 09:11:28 -0700151 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800152 setScaleType(ScaleType.CENTER);
153 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Selim Cinek49014f82016-11-04 14:55:30 -0700154 if (mNotification != null) {
Selim Cinek875ba9b2017-02-13 16:20:17 -0800155 setDecorColor(getContext().getColor(
Selim Cinek49014f82016-11-04 14:55:30 -0700156 com.android.internal.R.color.notification_icon_default_color));
157 }
158 reloadDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800159 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400160
Adrian Roos138f0342017-04-28 09:11:28 -0700161 private void maybeUpdateIconScaleDimens() {
Daniel Sandler7579bca2011-08-18 15:47:26 -0400162 // We do not resize and scale system icons (on the right), only notification icons (on the
163 // left).
Selim Cinek3e7592d2016-04-11 09:35:54 +0800164 if (mNotification != null || mAlwaysScaleIcon) {
Adrian Roos138f0342017-04-28 09:11:28 -0700165 updateIconScaleDimens();
Daniel Sandler7579bca2011-08-18 15:47:26 -0400166 }
Selim Cinek3e7592d2016-04-11 09:35:54 +0800167 }
Daniel Sandlerabff0322011-09-27 11:19:34 -0400168
Adrian Roos138f0342017-04-28 09:11:28 -0700169 private void updateIconScaleDimens() {
Selim Cinek3e7592d2016-04-11 09:35:54 +0800170 Resources res = mContext.getResources();
Adrian Roos138f0342017-04-28 09:11:28 -0700171 mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
172 mStatusBarIconDrawingSizeDark =
173 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
174 mStatusBarIconDrawingSize =
175 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
176 updateIconScale();
177 }
178
179 private void updateIconScale() {
180 final float imageBounds = NotificationUtils.interpolate(
181 mStatusBarIconDrawingSize,
182 mStatusBarIconDrawingSizeDark,
183 mDarkAmount);
184 final int outerBounds = mStatusBarIconSize;
Selim Cinek281c2022016-10-13 19:14:43 -0700185 mIconScale = (float)imageBounds / (float)outerBounds;
Selim Cinek281c2022016-10-13 19:14:43 -0700186 }
187
Adrian Roosceac4a02017-05-30 20:25:52 -0700188 public float getIconScaleFullyDark() {
189 return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize;
190 }
191
Selim Cinek281c2022016-10-13 19:14:43 -0700192 public float getIconScale() {
193 return mIconScale;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800194 }
195
196 @Override
197 protected void onConfigurationChanged(Configuration newConfig) {
198 super.onConfigurationChanged(newConfig);
199 int density = newConfig.densityDpi;
200 if (density != mDensity) {
201 mDensity = density;
Adrian Roos138f0342017-04-28 09:11:28 -0700202 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800203 updateDrawable();
Selim Cinek49014f82016-11-04 14:55:30 -0700204 reloadDimens();
205 }
206 }
207
208 private void reloadDimens() {
209 boolean applyRadius = mDotRadius == mStaticDotRadius;
210 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
211 if (applyRadius) {
212 mDotRadius = mStaticDotRadius;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800213 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700214 }
215
Selim Cinek72fc8db2017-06-06 18:07:47 -0700216 public void setNotification(StatusBarNotification notification) {
Christoph Studera0506e72014-07-31 20:27:39 +0200217 mNotification = notification;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700218 if (notification != null) {
219 setContentDescription(notification.getNotification());
220 }
Christoph Studera0506e72014-07-31 20:27:39 +0200221 }
222
Daniel Sandler05e24142011-11-10 11:56:49 -0500223 public StatusBarIconView(Context context, AttributeSet attrs) {
224 super(context, attrs);
Adrian Roos456e0052017-04-04 16:44:29 -0700225 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400226 mBlocked = false;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800227 mAlwaysScaleIcon = true;
Adrian Roos138f0342017-04-28 09:11:28 -0700228 updateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800229 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Daniel Sandler05e24142011-11-10 11:56:49 -0500230 }
231
Joe Onorato0cbda992010-05-02 16:28:15 -0700232 private static boolean streq(String a, String b) {
Joe Onorato66d7d012010-05-14 10:05:10 -0700233 if (a == b) {
234 return true;
235 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700236 if (a == null && b != null) {
237 return false;
238 }
239 if (a != null && b == null) {
240 return false;
241 }
242 return a.equals(b);
243 }
244
Dan Sandlerd63f9322015-05-06 15:18:49 -0400245 public boolean equalIcons(Icon a, Icon b) {
246 if (a == b) return true;
247 if (a.getType() != b.getType()) return false;
248 switch (a.getType()) {
249 case Icon.TYPE_RESOURCE:
250 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
251 case Icon.TYPE_URI:
252 return a.getUriString().equals(b.getUriString());
253 default:
254 return false;
255 }
256 }
Joe Onorato005847b2010-06-04 16:08:02 -0400257 /**
258 * Returns whether the set succeeded.
259 */
260 public boolean set(StatusBarIcon icon) {
Dan Sandlerd63f9322015-05-06 15:18:49 -0400261 final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
Joe Onorato005847b2010-06-04 16:08:02 -0400262 final boolean levelEquals = iconEquals
263 && mIcon.iconLevel == icon.iconLevel;
264 final boolean visibilityEquals = mIcon != null
265 && mIcon.visible == icon.visible;
Joe Onorato6c01a112010-10-04 17:38:47 -0400266 final boolean numberEquals = mIcon != null
267 && mIcon.number == icon.number;
268 mIcon = icon.clone();
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700269 setContentDescription(icon.contentDescription);
Joe Onorato005847b2010-06-04 16:08:02 -0400270 if (!iconEquals) {
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800271 if (!updateDrawable(false /* no clear */)) return false;
Selim Cinek2b6eb8032016-12-29 14:22:21 +0100272 // we have to clear the grayscale tag since it may have changed
273 setTag(R.id.icon_is_grayscale, null);
Joe Onorato0cbda992010-05-02 16:28:15 -0700274 }
Joe Onorato005847b2010-06-04 16:08:02 -0400275 if (!levelEquals) {
276 setImageLevel(icon.iconLevel);
Joe Onorato0cbda992010-05-02 16:28:15 -0700277 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400278
Joe Onorato6c01a112010-10-04 17:38:47 -0400279 if (!numberEquals) {
John Spurlock01534782014-01-13 11:59:22 -0500280 if (icon.number > 0 && getContext().getResources().getBoolean(
Joe Onorato8595a3d2010-11-19 18:12:07 -0800281 R.bool.config_statusBarShowNumber)) {
Joe Onorato6c01a112010-10-04 17:38:47 -0400282 if (mNumberBackground == null) {
283 mNumberBackground = getContext().getResources().getDrawable(
284 R.drawable.ic_notification_overlay);
285 }
286 placeNumber();
287 } else {
288 mNumberBackground = null;
289 mNumberText = null;
290 }
291 invalidate();
292 }
Joe Onorato005847b2010-06-04 16:08:02 -0400293 if (!visibilityEquals) {
Jason Monk3b230072015-05-29 11:11:29 -0400294 setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
Joe Onorato005847b2010-06-04 16:08:02 -0400295 }
Joe Onorato005847b2010-06-04 16:08:02 -0400296 return true;
Joe Onorato0cbda992010-05-02 16:28:15 -0700297 }
298
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800299 public void updateDrawable() {
300 updateDrawable(true /* with clear */);
301 }
302
303 private boolean updateDrawable(boolean withClear) {
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100304 if (mIcon == null) {
305 return false;
306 }
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800307 Drawable drawable;
308 try {
309 drawable = getIcon(mIcon);
310 } catch (OutOfMemoryError e) {
311 Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
312 return false;
313 }
314
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800315 if (drawable == null) {
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800316 Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800317 return false;
318 }
319 if (withClear) {
320 setImageDrawable(null);
321 }
322 setImageDrawable(drawable);
323 return true;
324 }
325
Selim Cinek72fc8db2017-06-06 18:07:47 -0700326 public Icon getSourceIcon() {
327 return mIcon.icon;
328 }
329
Joe Onoratof5510542010-06-01 07:46:41 -0700330 private Drawable getIcon(StatusBarIcon icon) {
331 return getIcon(getContext(), icon);
332 }
333
Joe Onorato0cbda992010-05-02 16:28:15 -0700334 /**
Dan Sandlerd63f9322015-05-06 15:18:49 -0400335 * Returns the right icon to use for this item
John Spurlock209bede2013-07-17 12:23:27 -0400336 *
Dan Sandlerd63f9322015-05-06 15:18:49 -0400337 * @param context Context to use to get resources
Joe Onorato0cbda992010-05-02 16:28:15 -0700338 * @return Drawable for this item, or null if the package or item could not
339 * be found
340 */
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800341 public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
342 int userId = statusBarIcon.user.getIdentifier();
Dan Sandlerd63f9322015-05-06 15:18:49 -0400343 if (userId == UserHandle.USER_ALL) {
Xiaohui Chen87d0e252015-07-30 15:38:16 -0700344 userId = UserHandle.USER_SYSTEM;
Joe Onorato0cbda992010-05-02 16:28:15 -0700345 }
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800346
347 Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
348
349 TypedValue typedValue = new TypedValue();
350 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
351 float scaleFactor = typedValue.getFloat();
352
353 // No need to scale the icon, so return it as is.
354 if (scaleFactor == 1.f) {
355 return icon;
356 }
357
358 return new ScalingDrawableWrapper(icon, scaleFactor);
Joe Onorato0cbda992010-05-02 16:28:15 -0700359 }
Joe Onoratob77f53b2010-05-28 19:59:51 -0400360
361 public StatusBarIcon getStatusBarIcon() {
362 return mIcon;
363 }
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700364
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700365 @Override
366 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
367 super.onInitializeAccessibilityEvent(event);
368 if (mNotification != null) {
Selim Cinek72fc8db2017-06-06 18:07:47 -0700369 event.setParcelableData(mNotification.getNotification());
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700370 }
371 }
372
373 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400374 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
375 super.onSizeChanged(w, h, oldw, oldh);
376 if (mNumberBackground != null) {
377 placeNumber();
378 }
379 }
380
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700381 @Override
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100382 public void onRtlPropertiesChanged(int layoutDirection) {
383 super.onRtlPropertiesChanged(layoutDirection);
384 updateDrawable();
385 }
386
387 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400388 protected void onDraw(Canvas canvas) {
Selim Cinek49014f82016-11-04 14:55:30 -0700389 if (mIconAppearAmount > 0.0f) {
390 canvas.save();
391 canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
392 getWidth() / 2, getHeight() / 2);
393 super.onDraw(canvas);
394 canvas.restore();
395 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400396
397 if (mNumberBackground != null) {
398 mNumberBackground.draw(canvas);
399 canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
400 }
Selim Cinek49014f82016-11-04 14:55:30 -0700401 if (mDotAppearAmount != 0.0f) {
402 float radius;
403 float alpha;
404 if (mDotAppearAmount <= 1.0f) {
405 radius = mDotRadius * mDotAppearAmount;
406 alpha = 1.0f;
407 } else {
408 float fadeOutAmount = mDotAppearAmount - 1.0f;
409 alpha = 1.0f - fadeOutAmount;
410 radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
411 }
412 mDotPaint.setAlpha((int) (alpha * 255));
413 canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
414 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400415 }
416
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700417 @Override
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700418 protected void debug(int depth) {
419 super.debug(depth);
420 Log.d("View", debugIndent(depth) + "slot=" + mSlot);
421 Log.d("View", debugIndent(depth) + "icon=" + mIcon);
422 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400423
424 void placeNumber() {
Daniel Sandlerebce0112011-06-16 16:44:51 -0400425 final String str;
John Spurlock01534782014-01-13 11:59:22 -0500426 final int tooBig = getContext().getResources().getInteger(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400427 android.R.integer.status_bar_notification_info_maxnum);
428 if (mIcon.number > tooBig) {
John Spurlock01534782014-01-13 11:59:22 -0500429 str = getContext().getResources().getString(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400430 android.R.string.status_bar_notification_info_overflow);
431 } else {
432 NumberFormat f = NumberFormat.getIntegerInstance();
433 str = f.format(mIcon.number);
434 }
435 mNumberText = str;
436
Joe Onorato6c01a112010-10-04 17:38:47 -0400437 final int w = getWidth();
438 final int h = getHeight();
439 final Rect r = new Rect();
440 mNumberPain.getTextBounds(str, 0, str.length(), r);
441 final int tw = r.right - r.left;
442 final int th = r.bottom - r.top;
443 mNumberBackground.getPadding(r);
444 int dw = r.left + tw + r.right;
445 if (dw < mNumberBackground.getMinimumWidth()) {
446 dw = mNumberBackground.getMinimumWidth();
447 }
448 mNumberX = w-r.right-((dw-r.right-r.left)/2);
449 int dh = r.top + th + r.bottom;
450 if (dh < mNumberBackground.getMinimumWidth()) {
451 dh = mNumberBackground.getMinimumWidth();
452 }
453 mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
454 mNumberBackground.setBounds(w-dw, h-dh, w, h);
455 }
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700456
457 private void setContentDescription(Notification notification) {
458 if (notification != null) {
Adrian Rooseba05822016-04-22 17:09:27 -0700459 String d = contentDescForNotification(mContext, notification);
460 if (!TextUtils.isEmpty(d)) {
461 setContentDescription(d);
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700462 }
463 }
464 }
Daniel Sandler7579bca2011-08-18 15:47:26 -0400465
466 public String toString() {
John Spurlock209bede2013-07-17 12:23:27 -0400467 return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
Daniel Sandler7579bca2011-08-18 15:47:26 -0400468 + " notification=" + mNotification + ")";
469 }
Jason Monk3b230072015-05-29 11:11:29 -0400470
Selim Cinek72fc8db2017-06-06 18:07:47 -0700471 public StatusBarNotification getNotification() {
472 return mNotification;
473 }
474
Jason Monk3b230072015-05-29 11:11:29 -0400475 public String getSlot() {
476 return mSlot;
477 }
Adrian Rooseba05822016-04-22 17:09:27 -0700478
479
480 public static String contentDescForNotification(Context c, Notification n) {
Adrian Roosa8e18ef2016-05-26 17:17:02 -0700481 String appName = "";
482 try {
483 Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
484 appName = builder.loadHeaderAppName();
485 } catch (RuntimeException e) {
486 Log.e(TAG, "Unable to recover builder", e);
487 // Trying to get the app name from the app info instead.
488 Parcelable appInfo = n.extras.getParcelable(
489 Notification.EXTRA_BUILDER_APPLICATION_INFO);
490 if (appInfo instanceof ApplicationInfo) {
491 appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel(
492 c.getPackageManager()));
493 }
494 }
Adrian Roos51548e62016-04-28 11:20:26 -0700495
Julia Reynolds7f9ce782016-05-17 15:34:34 -0400496 CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
Adrian Roosf76600e2017-04-19 15:32:43 -0700497 CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
Adrian Rooseba05822016-04-22 17:09:27 -0700498 CharSequence ticker = n.tickerText;
Adrian Roos51548e62016-04-28 11:20:26 -0700499
Adrian Roosf76600e2017-04-19 15:32:43 -0700500 // Some apps just put the app name into the title
501 CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
502
503 CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
Adrian Roos6e782a52017-03-30 18:34:10 -0700504 : !TextUtils.isEmpty(ticker) ? ticker : "";
Adrian Roos51548e62016-04-28 11:20:26 -0700505
506 return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
Adrian Rooseba05822016-04-22 17:09:27 -0700507 }
Adrian Roos51548e62016-04-28 11:20:26 -0700508
Selim Cinek875ba9b2017-02-13 16:20:17 -0800509 /**
510 * Set the color that is used to draw decoration like the overflow dot. This will not be applied
511 * to the drawable.
512 */
513 public void setDecorColor(int iconTint) {
Adrian Roos456e0052017-04-04 16:44:29 -0700514 mDecorColor = iconTint;
515 updateDecorColor();
516 }
517
518 private void updateDecorColor() {
519 int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
520 if (mDotPaint.getColor() != color) {
521 mDotPaint.setColor(color);
522
523 if (mDotAppearAmount != 0) {
524 invalidate();
525 }
526 }
Selim Cinek49014f82016-11-04 14:55:30 -0700527 }
528
Selim Cinek875ba9b2017-02-13 16:20:17 -0800529 /**
530 * Set the static color that should be used for the drawable of this icon if it's not
531 * transitioning this also immediately sets the color.
532 */
533 public void setStaticDrawableColor(int color) {
534 mDrawableColor = color;
535 setColorInternal(color);
Lucas Dupin83519da2017-06-21 11:58:31 -0700536 updateContrastedStaticColor();
Selim Cinek875ba9b2017-02-13 16:20:17 -0800537 mIconColor = color;
Adrian Roos456e0052017-04-04 16:44:29 -0700538 mDozer.setColor(color);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800539 }
540
541 private void setColorInternal(int color) {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700542 mCurrentSetColor = color;
543 updateIconColor();
544 }
545
546 private void updateIconColor() {
547 if (mCurrentSetColor != NO_COLOR) {
548 setImageTintList(ColorStateList.valueOf(NotificationUtils.interpolateColors(
549 mCurrentSetColor, Color.WHITE, mDarkAmount)));
Selim Cinek875ba9b2017-02-13 16:20:17 -0800550 } else {
551 setImageTintList(null);
Adrian Roosf3eacd32017-04-14 16:35:58 -0700552 mDozer.updateGrayscale(this, mDarkAmount);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800553 }
Selim Cinek875ba9b2017-02-13 16:20:17 -0800554 }
555
556 public void setIconColor(int iconColor, boolean animate) {
557 if (mIconColor != iconColor) {
558 mIconColor = iconColor;
559 if (mColorAnimator != null) {
560 mColorAnimator.cancel();
561 }
562 if (mCurrentSetColor == iconColor) {
563 return;
564 }
565 if (animate && mCurrentSetColor != NO_COLOR) {
566 mAnimationStartColor = mCurrentSetColor;
567 mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
568 mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
569 mColorAnimator.setDuration(ANIMATION_DURATION_FAST);
570 mColorAnimator.addUpdateListener(mColorUpdater);
571 mColorAnimator.addListener(new AnimatorListenerAdapter() {
572 @Override
573 public void onAnimationEnd(Animator animation) {
574 mColorAnimator = null;
575 mAnimationStartColor = NO_COLOR;
576 }
577 });
578 mColorAnimator.start();
579 } else {
580 setColorInternal(iconColor);
581 }
582 }
583 }
584
585 public int getStaticDrawableColor() {
586 return mDrawableColor;
587 }
588
Lucas Dupin83519da2017-06-21 11:58:31 -0700589 /**
590 * A drawable color that passes GAR on a specific background.
591 * This value is cached.
592 *
593 * @param backgroundColor Background to test against.
594 * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}.
595 */
596 int getContrastedStaticDrawableColor(int backgroundColor) {
597 if (mCachedContrastBackgroundColor != backgroundColor) {
598 mCachedContrastBackgroundColor = backgroundColor;
599 updateContrastedStaticColor();
600 }
601 return mContrastedDrawableColor;
602 }
603
604 private void updateContrastedStaticColor() {
Lucas Dupinddbaf842017-06-23 09:30:49 -0700605 if (Color.alpha(mCachedContrastBackgroundColor) != 255) {
606 mContrastedDrawableColor = mDrawableColor;
Lucas Dupin83519da2017-06-21 11:58:31 -0700607 return;
608 }
609 // We'll modify the color if it doesn't pass GAR
610 int contrastedColor = mDrawableColor;
611 if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor,
612 contrastedColor)) {
613 float[] hsl = new float[3];
614 ColorUtils.colorToHSL(mDrawableColor, hsl);
615 // This is basically a light grey, pushing the color will only distort it.
616 // Best thing to do in here is to fallback to the default color.
617 if (hsl[1] < 0.2f) {
618 contrastedColor = Notification.COLOR_DEFAULT;
619 }
620 contrastedColor = NotificationColorUtil.resolveContrastColor(mContext,
621 contrastedColor, mCachedContrastBackgroundColor);
622 }
623 mContrastedDrawableColor = contrastedColor;
624 }
625
Selim Cinek5b5beb012016-11-08 18:11:58 -0800626 public void setVisibleState(int state) {
627 setVisibleState(state, true /* animate */, null /* endRunnable */);
628 }
629
630 public void setVisibleState(int state, boolean animate) {
631 setVisibleState(state, animate, null);
632 }
633
634 @Override
635 public boolean hasOverlappingRendering() {
636 return false;
637 }
638
639 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800640 boolean runnableAdded = false;
Selim Cinek49014f82016-11-04 14:55:30 -0700641 if (visibleState != mVisibleState) {
642 mVisibleState = visibleState;
Selim Cinek5ece5a72017-02-15 11:47:42 -0800643 if (mIconAppearAnimator != null) {
644 mIconAppearAnimator.cancel();
645 }
646 if (mDotAnimator != null) {
647 mDotAnimator.cancel();
648 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800649 if (animate) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800650 float targetAmount = 0.0f;
651 Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
652 if (visibleState == STATE_ICON) {
653 targetAmount = 1.0f;
654 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
655 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800656 float currentAmount = getIconAppearAmount();
657 if (targetAmount != currentAmount) {
658 mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
659 currentAmount, targetAmount);
660 mIconAppearAnimator.setInterpolator(interpolator);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800661 mIconAppearAnimator.setDuration(ANIMATION_DURATION_FAST);
Selim Cinek65d418e2016-11-29 15:42:34 -0800662 mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
663 @Override
664 public void onAnimationEnd(Animator animation) {
665 mIconAppearAnimator = null;
666 runRunnable(endRunnable);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800667 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800668 });
669 mIconAppearAnimator.start();
670 runnableAdded = true;
671 }
Selim Cinek49014f82016-11-04 14:55:30 -0700672
Selim Cinek5b5beb012016-11-08 18:11:58 -0800673 targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
674 interpolator = Interpolators.FAST_OUT_LINEAR_IN;
675 if (visibleState == STATE_DOT) {
676 targetAmount = 1.0f;
677 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
678 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800679 currentAmount = getDotAppearAmount();
680 if (targetAmount != currentAmount) {
681 mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
682 currentAmount, targetAmount);
683 mDotAnimator.setInterpolator(interpolator);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800684 mDotAnimator.setDuration(ANIMATION_DURATION_FAST);
Selim Cinek65d418e2016-11-29 15:42:34 -0800685 final boolean runRunnable = !runnableAdded;
686 mDotAnimator.addListener(new AnimatorListenerAdapter() {
687 @Override
688 public void onAnimationEnd(Animator animation) {
689 mDotAnimator = null;
690 if (runRunnable) {
691 runRunnable(endRunnable);
692 }
693 }
694 });
695 mDotAnimator.start();
696 runnableAdded = true;
697 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800698 } else {
699 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
Selim Cinek01a73f92016-12-06 16:13:42 -0800700 setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
701 : visibleState == STATE_ICON ? 2.0f
702 : 0.0f);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800703 }
Selim Cinek49014f82016-11-04 14:55:30 -0700704 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800705 if (!runnableAdded) {
706 runRunnable(endRunnable);
707 }
708 }
709
710 private void runRunnable(Runnable runnable) {
711 if (runnable != null) {
712 runnable.run();
713 }
Selim Cinek49014f82016-11-04 14:55:30 -0700714 }
715
Selim Cinek5b5beb012016-11-08 18:11:58 -0800716 public void setIconAppearAmount(float iconAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800717 if (mIconAppearAmount != iconAppearAmount) {
718 mIconAppearAmount = iconAppearAmount;
719 invalidate();
720 }
Selim Cinek49014f82016-11-04 14:55:30 -0700721 }
722
723 public float getIconAppearAmount() {
724 return mIconAppearAmount;
725 }
726
727 public int getVisibleState() {
728 return mVisibleState;
729 }
730
731 public void setDotAppearAmount(float dotAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800732 if (mDotAppearAmount != dotAppearAmount) {
733 mDotAppearAmount = dotAppearAmount;
734 invalidate();
735 }
Selim Cinek49014f82016-11-04 14:55:30 -0700736 }
737
Selim Cinek2b549f42016-11-22 16:38:51 -0800738 @Override
739 public void setVisibility(int visibility) {
740 super.setVisibility(visibility);
741 if (mOnVisibilityChangedListener != null) {
742 mOnVisibilityChangedListener.onVisibilityChanged(visibility);
743 }
744 }
745
Selim Cinek49014f82016-11-04 14:55:30 -0700746 public float getDotAppearAmount() {
747 return mDotAppearAmount;
748 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800749
750 public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
751 mOnVisibilityChangedListener = listener;
752 }
753
Adrian Roos456e0052017-04-04 16:44:29 -0700754 public void setDark(boolean dark, boolean fade, long delay) {
Adrian Roos456e0052017-04-04 16:44:29 -0700755 mDozer.setIntensityDark(f -> {
756 mDarkAmount = f;
Adrian Roos138f0342017-04-28 09:11:28 -0700757 updateIconScale();
Adrian Roos456e0052017-04-04 16:44:29 -0700758 updateDecorColor();
Adrian Roosf3eacd32017-04-14 16:35:58 -0700759 updateIconColor();
Adrian Roosfe0071f2017-07-06 18:45:10 +0200760 updateAllowAnimation();
Adrian Roos456e0052017-04-04 16:44:29 -0700761 }, dark, fade, delay);
762 }
763
Adrian Roosfe0071f2017-07-06 18:45:10 +0200764 private void updateAllowAnimation() {
765 if (mDarkAmount == 0 || mDarkAmount == 1) {
766 setAllowAnimation(mDarkAmount == 0);
767 }
768 }
769
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700770 public void setIsInShelf(boolean isInShelf) {
771 mIsInShelf = isInShelf;
772 }
773
774 public boolean isInShelf() {
775 return mIsInShelf;
776 }
777
Selim Cinek2b549f42016-11-22 16:38:51 -0800778 public interface OnVisibilityChangedListener {
779 void onVisibilityChanged(int newVisibility);
780 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700781}