blob: bd6bd12a5e9bef0de638f92cc020ca7766553320 [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
Evan Lairde1d13c92018-03-20 16:58:01 -040019import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
20
Selim Cinek49014f82016-11-04 14:55:30 -070021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.ObjectAnimator;
Selim Cinek875ba9b2017-02-13 16:20:17 -080024import android.animation.ValueAnimator;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070025import android.app.Notification;
Joe Onorato0cbda992010-05-02 16:28:15 -070026import android.content.Context;
Adrian Roosa8e18ef2016-05-26 17:17:02 -070027import android.content.pm.ApplicationInfo;
Evan Lairde1d13c92018-03-20 16:58:01 -040028import android.content.res.ColorStateList;
Selim Cinek3e7592d2016-04-11 09:35:54 +080029import android.content.res.Configuration;
Joe Onorato0cbda992010-05-02 16:28:15 -070030import android.content.res.Resources;
Joe Onorato0cbda992010-05-02 16:28:15 -070031import android.graphics.Canvas;
Adrian Roos456e0052017-04-04 16:44:29 -070032import android.graphics.Color;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020033import android.graphics.ColorMatrixColorFilter;
Joe Onorato6c01a112010-10-04 17:38:47 -040034import android.graphics.Paint;
35import android.graphics.Rect;
John Spurlockde84f0e2013-06-12 12:41:00 -040036import android.graphics.drawable.Drawable;
Dan Sandlerd63f9322015-05-06 15:18:49 -040037import android.graphics.drawable.Icon;
Adrian Roosa8e18ef2016-05-26 17:17:02 -070038import android.os.Parcelable;
Jeff Sharkeyded653b2012-09-27 19:09:24 -070039import android.os.UserHandle;
Selim Cinek72fc8db2017-06-06 18:07:47 -070040import android.service.notification.StatusBarNotification;
Lucas Dupin83519da2017-06-21 11:58:31 -070041import android.support.v4.graphics.ColorUtils;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070042import android.text.TextUtils;
Daniel Sandler05e24142011-11-10 11:56:49 -050043import android.util.AttributeSet;
Selim Cinek49014f82016-11-04 14:55:30 -070044import android.util.FloatProperty;
Joe Onoratof9ec03c2010-09-23 15:09:18 -070045import android.util.Log;
Selim Cinek49014f82016-11-04 14:55:30 -070046import android.util.Property;
Anthony Chen55e8e1e2016-01-08 10:31:46 -080047import android.util.TypedValue;
Joe Onorato0cbda992010-05-02 16:28:15 -070048import android.view.ViewDebug;
Svetoslav Ganov6179ea32011-06-28 01:12:41 -070049import android.view.accessibility.AccessibilityEvent;
Selim Cinek49014f82016-11-04 14:55:30 -070050import android.view.animation.Interpolator;
Winsonc0d70582016-01-29 10:24:39 -080051
Joe Onorato0cbda992010-05-02 16:28:15 -070052import com.android.internal.statusbar.StatusBarIcon;
Lucas Dupin83519da2017-06-21 11:58:31 -070053import com.android.internal.util.NotificationColorUtil;
Selim Cinek49014f82016-11-04 14:55:30 -070054import com.android.systemui.Interpolators;
Joe Onorato6c01a112010-10-04 17:38:47 -040055import com.android.systemui.R;
Adrian Roos456e0052017-04-04 16:44:29 -070056import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
Selim Cinek49014f82016-11-04 14:55:30 -070057import com.android.systemui.statusbar.notification.NotificationUtils;
Joe Onorato6c01a112010-10-04 17:38:47 -040058
John Spurlockde84f0e2013-06-12 12:41:00 -040059import java.text.NumberFormat;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020060import java.util.Arrays;
John Spurlockde84f0e2013-06-12 12:41:00 -040061
Evan Lairde1d13c92018-03-20 16:58:01 -040062public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {
Selim Cinek875ba9b2017-02-13 16:20:17 -080063 public static final int NO_COLOR = 0;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020064
65 /**
66 * Multiply alpha values with (1+DARK_ALPHA_BOOST) when dozing. The chosen value boosts
67 * everything above 30% to 50%, making it appear on 1bit color depths.
68 */
69 private static final float DARK_ALPHA_BOOST = 0.67f;
Selim Cinek875ba9b2017-02-13 16:20:17 -080070 private final int ANIMATION_DURATION_FAST = 100;
71
Selim Cinek49014f82016-11-04 14:55:30 -070072 public static final int STATE_ICON = 0;
73 public static final int STATE_DOT = 1;
74 public static final int STATE_HIDDEN = 2;
Joe Onorato0cbda992010-05-02 16:28:15 -070075
Selim Cinek49014f82016-11-04 14:55:30 -070076 private static final String TAG = "StatusBarIconView";
77 private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
78 = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
79
80 @Override
81 public void setValue(StatusBarIconView object, float value) {
82 object.setIconAppearAmount(value);
83 }
84
85 @Override
86 public Float get(StatusBarIconView object) {
87 return object.getIconAppearAmount();
88 }
89 };
Selim Cinek5b5beb012016-11-08 18:11:58 -080090 private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
Selim Cinek49014f82016-11-04 14:55:30 -070091 = new FloatProperty<StatusBarIconView>("dot_appear_amount") {
92
93 @Override
94 public void setValue(StatusBarIconView object, float value) {
95 object.setDotAppearAmount(value);
96 }
97
98 @Override
99 public Float get(StatusBarIconView object) {
100 return object.getDotAppearAmount();
101 }
102 };
103
104 private boolean mAlwaysScaleIcon;
Adrian Roos138f0342017-04-28 09:11:28 -0700105 private int mStatusBarIconDrawingSizeDark = 1;
106 private int mStatusBarIconDrawingSize = 1;
107 private int mStatusBarIconSize = 1;
Joe Onorato0cbda992010-05-02 16:28:15 -0700108 private StatusBarIcon mIcon;
109 @ViewDebug.ExportedProperty private String mSlot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400110 private Drawable mNumberBackground;
111 private Paint mNumberPain;
112 private int mNumberX;
113 private int mNumberY;
114 private String mNumberText;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700115 private StatusBarNotification mNotification;
Jason Monk3b230072015-05-29 11:11:29 -0400116 private final boolean mBlocked;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800117 private int mDensity;
Selim Cinek281c2022016-10-13 19:14:43 -0700118 private float mIconScale = 1.0f;
Selim Cinek49014f82016-11-04 14:55:30 -0700119 private final Paint mDotPaint = new Paint();
Selim Cinek49014f82016-11-04 14:55:30 -0700120 private float mDotRadius;
121 private int mStaticDotRadius;
122 private int mVisibleState = STATE_ICON;
123 private float mIconAppearAmount = 1.0f;
124 private ObjectAnimator mIconAppearAnimator;
125 private ObjectAnimator mDotAnimator;
126 private float mDotAppearAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800127 private OnVisibilityChangedListener mOnVisibilityChangedListener;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800128 private int mDrawableColor;
129 private int mIconColor;
Adrian Roos456e0052017-04-04 16:44:29 -0700130 private int mDecorColor;
131 private float mDarkAmount;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800132 private ValueAnimator mColorAnimator;
133 private int mCurrentSetColor = NO_COLOR;
134 private int mAnimationStartColor = NO_COLOR;
135 private final ValueAnimator.AnimatorUpdateListener mColorUpdater
136 = animation -> {
137 int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor,
138 animation.getAnimatedFraction());
139 setColorInternal(newColor);
140 };
Adrian Roos456e0052017-04-04 16:44:29 -0700141 private final NotificationIconDozeHelper mDozer;
Lucas Dupin83519da2017-06-21 11:58:31 -0700142 private int mContrastedDrawableColor;
143 private int mCachedContrastBackgroundColor = NO_COLOR;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200144 private float[] mMatrix;
145 private ColorMatrixColorFilter mMatrixColorFilter;
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700146 private boolean mIsInShelf;
Selim Cinek887da3c2017-10-06 13:37:32 -0700147 private Runnable mLayoutRunnable;
Selim Cinekd03518c2018-03-15 12:13:51 -0700148 private boolean mDismissed;
149 private Runnable mOnDismissListener;
Joe Onorato0cbda992010-05-02 16:28:15 -0700150
Selim Cinek72fc8db2017-06-06 18:07:47 -0700151 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
152 this(context, slot, sbn, false);
Jason Monk3b230072015-05-29 11:11:29 -0400153 }
154
Selim Cinek72fc8db2017-06-06 18:07:47 -0700155 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
Jason Monk3b230072015-05-29 11:11:29 -0400156 boolean blocked) {
Joe Onorato0cbda992010-05-02 16:28:15 -0700157 super(context);
Adrian Roos456e0052017-04-04 16:44:29 -0700158 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400159 mBlocked = blocked;
Joe Onorato0cbda992010-05-02 16:28:15 -0700160 mSlot = slot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400161 mNumberPain = new Paint();
162 mNumberPain.setTextAlign(Paint.Align.CENTER);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700163 mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
Joe Onorato6c01a112010-10-04 17:38:47 -0400164 mNumberPain.setAntiAlias(true);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700165 setNotification(sbn);
Adrian Roos138f0342017-04-28 09:11:28 -0700166 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800167 setScaleType(ScaleType.CENTER);
168 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Selim Cinek49014f82016-11-04 14:55:30 -0700169 if (mNotification != null) {
Selim Cinek875ba9b2017-02-13 16:20:17 -0800170 setDecorColor(getContext().getColor(
Selim Cinekc7f5a822018-03-20 19:32:06 -0700171 com.android.internal.R.color.notification_default_color_light));
Selim Cinek49014f82016-11-04 14:55:30 -0700172 }
173 reloadDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800174 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400175
Adrian Roos138f0342017-04-28 09:11:28 -0700176 private void maybeUpdateIconScaleDimens() {
Daniel Sandler7579bca2011-08-18 15:47:26 -0400177 // We do not resize and scale system icons (on the right), only notification icons (on the
178 // left).
Selim Cinek3e7592d2016-04-11 09:35:54 +0800179 if (mNotification != null || mAlwaysScaleIcon) {
Adrian Roos138f0342017-04-28 09:11:28 -0700180 updateIconScaleDimens();
Daniel Sandler7579bca2011-08-18 15:47:26 -0400181 }
Selim Cinek3e7592d2016-04-11 09:35:54 +0800182 }
Daniel Sandlerabff0322011-09-27 11:19:34 -0400183
Adrian Roos138f0342017-04-28 09:11:28 -0700184 private void updateIconScaleDimens() {
Selim Cinek3e7592d2016-04-11 09:35:54 +0800185 Resources res = mContext.getResources();
Adrian Roos138f0342017-04-28 09:11:28 -0700186 mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
187 mStatusBarIconDrawingSizeDark =
188 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
189 mStatusBarIconDrawingSize =
190 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
191 updateIconScale();
192 }
193
194 private void updateIconScale() {
195 final float imageBounds = NotificationUtils.interpolate(
196 mStatusBarIconDrawingSize,
197 mStatusBarIconDrawingSizeDark,
198 mDarkAmount);
199 final int outerBounds = mStatusBarIconSize;
Selim Cinek281c2022016-10-13 19:14:43 -0700200 mIconScale = (float)imageBounds / (float)outerBounds;
Selim Cinek281c2022016-10-13 19:14:43 -0700201 }
202
Adrian Roosceac4a02017-05-30 20:25:52 -0700203 public float getIconScaleFullyDark() {
204 return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize;
205 }
206
Selim Cinek281c2022016-10-13 19:14:43 -0700207 public float getIconScale() {
208 return mIconScale;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800209 }
210
211 @Override
212 protected void onConfigurationChanged(Configuration newConfig) {
213 super.onConfigurationChanged(newConfig);
214 int density = newConfig.densityDpi;
215 if (density != mDensity) {
216 mDensity = density;
Adrian Roos138f0342017-04-28 09:11:28 -0700217 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800218 updateDrawable();
Selim Cinek49014f82016-11-04 14:55:30 -0700219 reloadDimens();
220 }
221 }
222
223 private void reloadDimens() {
224 boolean applyRadius = mDotRadius == mStaticDotRadius;
225 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
226 if (applyRadius) {
227 mDotRadius = mStaticDotRadius;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800228 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700229 }
230
Selim Cinek72fc8db2017-06-06 18:07:47 -0700231 public void setNotification(StatusBarNotification notification) {
Christoph Studera0506e72014-07-31 20:27:39 +0200232 mNotification = notification;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700233 if (notification != null) {
234 setContentDescription(notification.getNotification());
235 }
Christoph Studera0506e72014-07-31 20:27:39 +0200236 }
237
Daniel Sandler05e24142011-11-10 11:56:49 -0500238 public StatusBarIconView(Context context, AttributeSet attrs) {
239 super(context, attrs);
Adrian Roos456e0052017-04-04 16:44:29 -0700240 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400241 mBlocked = false;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800242 mAlwaysScaleIcon = true;
Adrian Roos138f0342017-04-28 09:11:28 -0700243 updateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800244 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Daniel Sandler05e24142011-11-10 11:56:49 -0500245 }
246
Joe Onorato0cbda992010-05-02 16:28:15 -0700247 private static boolean streq(String a, String b) {
Joe Onorato66d7d012010-05-14 10:05:10 -0700248 if (a == b) {
249 return true;
250 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700251 if (a == null && b != null) {
252 return false;
253 }
254 if (a != null && b == null) {
255 return false;
256 }
257 return a.equals(b);
258 }
259
Dan Sandlerd63f9322015-05-06 15:18:49 -0400260 public boolean equalIcons(Icon a, Icon b) {
261 if (a == b) return true;
262 if (a.getType() != b.getType()) return false;
263 switch (a.getType()) {
264 case Icon.TYPE_RESOURCE:
265 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
266 case Icon.TYPE_URI:
267 return a.getUriString().equals(b.getUriString());
268 default:
269 return false;
270 }
271 }
Joe Onorato005847b2010-06-04 16:08:02 -0400272 /**
273 * Returns whether the set succeeded.
274 */
275 public boolean set(StatusBarIcon icon) {
Dan Sandlerd63f9322015-05-06 15:18:49 -0400276 final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
Joe Onorato005847b2010-06-04 16:08:02 -0400277 final boolean levelEquals = iconEquals
278 && mIcon.iconLevel == icon.iconLevel;
279 final boolean visibilityEquals = mIcon != null
280 && mIcon.visible == icon.visible;
Joe Onorato6c01a112010-10-04 17:38:47 -0400281 final boolean numberEquals = mIcon != null
282 && mIcon.number == icon.number;
283 mIcon = icon.clone();
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700284 setContentDescription(icon.contentDescription);
Joe Onorato005847b2010-06-04 16:08:02 -0400285 if (!iconEquals) {
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800286 if (!updateDrawable(false /* no clear */)) return false;
Selim Cinek2b6eb8032016-12-29 14:22:21 +0100287 // we have to clear the grayscale tag since it may have changed
288 setTag(R.id.icon_is_grayscale, null);
Joe Onorato0cbda992010-05-02 16:28:15 -0700289 }
Joe Onorato005847b2010-06-04 16:08:02 -0400290 if (!levelEquals) {
291 setImageLevel(icon.iconLevel);
Joe Onorato0cbda992010-05-02 16:28:15 -0700292 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400293
Joe Onorato6c01a112010-10-04 17:38:47 -0400294 if (!numberEquals) {
John Spurlock01534782014-01-13 11:59:22 -0500295 if (icon.number > 0 && getContext().getResources().getBoolean(
Joe Onorato8595a3d2010-11-19 18:12:07 -0800296 R.bool.config_statusBarShowNumber)) {
Joe Onorato6c01a112010-10-04 17:38:47 -0400297 if (mNumberBackground == null) {
298 mNumberBackground = getContext().getResources().getDrawable(
299 R.drawable.ic_notification_overlay);
300 }
301 placeNumber();
302 } else {
303 mNumberBackground = null;
304 mNumberText = null;
305 }
306 invalidate();
307 }
Joe Onorato005847b2010-06-04 16:08:02 -0400308 if (!visibilityEquals) {
Jason Monk3b230072015-05-29 11:11:29 -0400309 setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
Joe Onorato005847b2010-06-04 16:08:02 -0400310 }
Joe Onorato005847b2010-06-04 16:08:02 -0400311 return true;
Joe Onorato0cbda992010-05-02 16:28:15 -0700312 }
313
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800314 public void updateDrawable() {
315 updateDrawable(true /* with clear */);
316 }
317
318 private boolean updateDrawable(boolean withClear) {
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100319 if (mIcon == null) {
320 return false;
321 }
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800322 Drawable drawable;
323 try {
324 drawable = getIcon(mIcon);
325 } catch (OutOfMemoryError e) {
326 Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
327 return false;
328 }
329
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800330 if (drawable == null) {
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800331 Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800332 return false;
333 }
334 if (withClear) {
335 setImageDrawable(null);
336 }
337 setImageDrawable(drawable);
338 return true;
339 }
340
Selim Cinek72fc8db2017-06-06 18:07:47 -0700341 public Icon getSourceIcon() {
342 return mIcon.icon;
343 }
344
Joe Onoratof5510542010-06-01 07:46:41 -0700345 private Drawable getIcon(StatusBarIcon icon) {
346 return getIcon(getContext(), icon);
347 }
348
Joe Onorato0cbda992010-05-02 16:28:15 -0700349 /**
Dan Sandlerd63f9322015-05-06 15:18:49 -0400350 * Returns the right icon to use for this item
John Spurlock209bede2013-07-17 12:23:27 -0400351 *
Dan Sandlerd63f9322015-05-06 15:18:49 -0400352 * @param context Context to use to get resources
Joe Onorato0cbda992010-05-02 16:28:15 -0700353 * @return Drawable for this item, or null if the package or item could not
354 * be found
355 */
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800356 public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
357 int userId = statusBarIcon.user.getIdentifier();
Dan Sandlerd63f9322015-05-06 15:18:49 -0400358 if (userId == UserHandle.USER_ALL) {
Xiaohui Chen87d0e252015-07-30 15:38:16 -0700359 userId = UserHandle.USER_SYSTEM;
Joe Onorato0cbda992010-05-02 16:28:15 -0700360 }
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800361
362 Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
363
364 TypedValue typedValue = new TypedValue();
365 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
366 float scaleFactor = typedValue.getFloat();
367
368 // No need to scale the icon, so return it as is.
369 if (scaleFactor == 1.f) {
370 return icon;
371 }
372
373 return new ScalingDrawableWrapper(icon, scaleFactor);
Joe Onorato0cbda992010-05-02 16:28:15 -0700374 }
Joe Onoratob77f53b2010-05-28 19:59:51 -0400375
376 public StatusBarIcon getStatusBarIcon() {
377 return mIcon;
378 }
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700379
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700380 @Override
381 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
382 super.onInitializeAccessibilityEvent(event);
383 if (mNotification != null) {
Selim Cinek72fc8db2017-06-06 18:07:47 -0700384 event.setParcelableData(mNotification.getNotification());
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700385 }
386 }
387
388 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400389 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
390 super.onSizeChanged(w, h, oldw, oldh);
391 if (mNumberBackground != null) {
392 placeNumber();
393 }
394 }
395
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700396 @Override
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100397 public void onRtlPropertiesChanged(int layoutDirection) {
398 super.onRtlPropertiesChanged(layoutDirection);
399 updateDrawable();
400 }
401
402 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400403 protected void onDraw(Canvas canvas) {
Selim Cinek49014f82016-11-04 14:55:30 -0700404 if (mIconAppearAmount > 0.0f) {
405 canvas.save();
406 canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
407 getWidth() / 2, getHeight() / 2);
408 super.onDraw(canvas);
409 canvas.restore();
410 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400411
412 if (mNumberBackground != null) {
413 mNumberBackground.draw(canvas);
414 canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
415 }
Selim Cinek49014f82016-11-04 14:55:30 -0700416 if (mDotAppearAmount != 0.0f) {
417 float radius;
418 float alpha;
419 if (mDotAppearAmount <= 1.0f) {
420 radius = mDotRadius * mDotAppearAmount;
421 alpha = 1.0f;
422 } else {
423 float fadeOutAmount = mDotAppearAmount - 1.0f;
424 alpha = 1.0f - fadeOutAmount;
425 radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
426 }
427 mDotPaint.setAlpha((int) (alpha * 255));
428 canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
429 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400430 }
431
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700432 @Override
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700433 protected void debug(int depth) {
434 super.debug(depth);
435 Log.d("View", debugIndent(depth) + "slot=" + mSlot);
436 Log.d("View", debugIndent(depth) + "icon=" + mIcon);
437 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400438
439 void placeNumber() {
Daniel Sandlerebce0112011-06-16 16:44:51 -0400440 final String str;
John Spurlock01534782014-01-13 11:59:22 -0500441 final int tooBig = getContext().getResources().getInteger(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400442 android.R.integer.status_bar_notification_info_maxnum);
443 if (mIcon.number > tooBig) {
John Spurlock01534782014-01-13 11:59:22 -0500444 str = getContext().getResources().getString(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400445 android.R.string.status_bar_notification_info_overflow);
446 } else {
447 NumberFormat f = NumberFormat.getIntegerInstance();
448 str = f.format(mIcon.number);
449 }
450 mNumberText = str;
451
Joe Onorato6c01a112010-10-04 17:38:47 -0400452 final int w = getWidth();
453 final int h = getHeight();
454 final Rect r = new Rect();
455 mNumberPain.getTextBounds(str, 0, str.length(), r);
456 final int tw = r.right - r.left;
457 final int th = r.bottom - r.top;
458 mNumberBackground.getPadding(r);
459 int dw = r.left + tw + r.right;
460 if (dw < mNumberBackground.getMinimumWidth()) {
461 dw = mNumberBackground.getMinimumWidth();
462 }
463 mNumberX = w-r.right-((dw-r.right-r.left)/2);
464 int dh = r.top + th + r.bottom;
465 if (dh < mNumberBackground.getMinimumWidth()) {
466 dh = mNumberBackground.getMinimumWidth();
467 }
468 mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
469 mNumberBackground.setBounds(w-dw, h-dh, w, h);
470 }
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700471
472 private void setContentDescription(Notification notification) {
473 if (notification != null) {
Adrian Rooseba05822016-04-22 17:09:27 -0700474 String d = contentDescForNotification(mContext, notification);
475 if (!TextUtils.isEmpty(d)) {
476 setContentDescription(d);
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700477 }
478 }
479 }
Daniel Sandler7579bca2011-08-18 15:47:26 -0400480
481 public String toString() {
John Spurlock209bede2013-07-17 12:23:27 -0400482 return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
Daniel Sandler7579bca2011-08-18 15:47:26 -0400483 + " notification=" + mNotification + ")";
484 }
Jason Monk3b230072015-05-29 11:11:29 -0400485
Selim Cinek72fc8db2017-06-06 18:07:47 -0700486 public StatusBarNotification getNotification() {
487 return mNotification;
488 }
489
Jason Monk3b230072015-05-29 11:11:29 -0400490 public String getSlot() {
491 return mSlot;
492 }
Adrian Rooseba05822016-04-22 17:09:27 -0700493
494
495 public static String contentDescForNotification(Context c, Notification n) {
Adrian Roosa8e18ef2016-05-26 17:17:02 -0700496 String appName = "";
497 try {
498 Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
499 appName = builder.loadHeaderAppName();
500 } catch (RuntimeException e) {
501 Log.e(TAG, "Unable to recover builder", e);
502 // Trying to get the app name from the app info instead.
503 Parcelable appInfo = n.extras.getParcelable(
504 Notification.EXTRA_BUILDER_APPLICATION_INFO);
505 if (appInfo instanceof ApplicationInfo) {
506 appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel(
507 c.getPackageManager()));
508 }
509 }
Adrian Roos51548e62016-04-28 11:20:26 -0700510
Julia Reynolds7f9ce782016-05-17 15:34:34 -0400511 CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
Adrian Roosf76600e2017-04-19 15:32:43 -0700512 CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
Adrian Rooseba05822016-04-22 17:09:27 -0700513 CharSequence ticker = n.tickerText;
Adrian Roos51548e62016-04-28 11:20:26 -0700514
Adrian Roosf76600e2017-04-19 15:32:43 -0700515 // Some apps just put the app name into the title
516 CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
517
518 CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
Adrian Roos6e782a52017-03-30 18:34:10 -0700519 : !TextUtils.isEmpty(ticker) ? ticker : "";
Adrian Roos51548e62016-04-28 11:20:26 -0700520
521 return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
Adrian Rooseba05822016-04-22 17:09:27 -0700522 }
Adrian Roos51548e62016-04-28 11:20:26 -0700523
Selim Cinek875ba9b2017-02-13 16:20:17 -0800524 /**
525 * Set the color that is used to draw decoration like the overflow dot. This will not be applied
526 * to the drawable.
527 */
528 public void setDecorColor(int iconTint) {
Adrian Roos456e0052017-04-04 16:44:29 -0700529 mDecorColor = iconTint;
530 updateDecorColor();
531 }
532
533 private void updateDecorColor() {
534 int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
535 if (mDotPaint.getColor() != color) {
536 mDotPaint.setColor(color);
537
538 if (mDotAppearAmount != 0) {
539 invalidate();
540 }
541 }
Selim Cinek49014f82016-11-04 14:55:30 -0700542 }
543
Selim Cinek875ba9b2017-02-13 16:20:17 -0800544 /**
545 * Set the static color that should be used for the drawable of this icon if it's not
546 * transitioning this also immediately sets the color.
547 */
548 public void setStaticDrawableColor(int color) {
549 mDrawableColor = color;
550 setColorInternal(color);
Lucas Dupin83519da2017-06-21 11:58:31 -0700551 updateContrastedStaticColor();
Selim Cinek875ba9b2017-02-13 16:20:17 -0800552 mIconColor = color;
Adrian Roos456e0052017-04-04 16:44:29 -0700553 mDozer.setColor(color);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800554 }
555
556 private void setColorInternal(int color) {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700557 mCurrentSetColor = color;
558 updateIconColor();
559 }
560
561 private void updateIconColor() {
562 if (mCurrentSetColor != NO_COLOR) {
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200563 if (mMatrixColorFilter == null) {
564 mMatrix = new float[4 * 5];
565 mMatrixColorFilter = new ColorMatrixColorFilter(mMatrix);
566 }
567 int color = NotificationUtils.interpolateColors(
568 mCurrentSetColor, Color.WHITE, mDarkAmount);
569 updateTintMatrix(mMatrix, color, DARK_ALPHA_BOOST * mDarkAmount);
570 mMatrixColorFilter.setColorMatrixArray(mMatrix);
571 setColorFilter(mMatrixColorFilter);
572 invalidate(); // setColorFilter only invalidates if the filter instance changed.
Selim Cinek875ba9b2017-02-13 16:20:17 -0800573 } else {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700574 mDozer.updateGrayscale(this, mDarkAmount);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800575 }
Selim Cinek875ba9b2017-02-13 16:20:17 -0800576 }
577
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200578 /**
579 * Updates {@param array} such that it represents a matrix that changes RGB to {@param color}
Adrian Roos24dbff32017-08-02 16:57:11 +0200580 * and multiplies the alpha channel with the color's alpha+{@param alphaBoost}.
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200581 */
582 private static void updateTintMatrix(float[] array, int color, float alphaBoost) {
583 Arrays.fill(array, 0);
584 array[4] = Color.red(color);
585 array[9] = Color.green(color);
586 array[14] = Color.blue(color);
Adrian Roos24dbff32017-08-02 16:57:11 +0200587 array[18] = Color.alpha(color) / 255f + alphaBoost;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200588 }
589
Selim Cinek875ba9b2017-02-13 16:20:17 -0800590 public void setIconColor(int iconColor, boolean animate) {
591 if (mIconColor != iconColor) {
592 mIconColor = iconColor;
593 if (mColorAnimator != null) {
594 mColorAnimator.cancel();
595 }
596 if (mCurrentSetColor == iconColor) {
597 return;
598 }
599 if (animate && mCurrentSetColor != NO_COLOR) {
600 mAnimationStartColor = mCurrentSetColor;
601 mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
602 mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
603 mColorAnimator.setDuration(ANIMATION_DURATION_FAST);
604 mColorAnimator.addUpdateListener(mColorUpdater);
605 mColorAnimator.addListener(new AnimatorListenerAdapter() {
606 @Override
607 public void onAnimationEnd(Animator animation) {
608 mColorAnimator = null;
609 mAnimationStartColor = NO_COLOR;
610 }
611 });
612 mColorAnimator.start();
613 } else {
614 setColorInternal(iconColor);
615 }
616 }
617 }
618
619 public int getStaticDrawableColor() {
620 return mDrawableColor;
621 }
622
Lucas Dupin83519da2017-06-21 11:58:31 -0700623 /**
624 * A drawable color that passes GAR on a specific background.
625 * This value is cached.
626 *
627 * @param backgroundColor Background to test against.
628 * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}.
629 */
630 int getContrastedStaticDrawableColor(int backgroundColor) {
631 if (mCachedContrastBackgroundColor != backgroundColor) {
632 mCachedContrastBackgroundColor = backgroundColor;
633 updateContrastedStaticColor();
634 }
635 return mContrastedDrawableColor;
636 }
637
638 private void updateContrastedStaticColor() {
Lucas Dupinddbaf842017-06-23 09:30:49 -0700639 if (Color.alpha(mCachedContrastBackgroundColor) != 255) {
640 mContrastedDrawableColor = mDrawableColor;
Lucas Dupin83519da2017-06-21 11:58:31 -0700641 return;
642 }
643 // We'll modify the color if it doesn't pass GAR
644 int contrastedColor = mDrawableColor;
645 if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor,
646 contrastedColor)) {
647 float[] hsl = new float[3];
648 ColorUtils.colorToHSL(mDrawableColor, hsl);
649 // This is basically a light grey, pushing the color will only distort it.
650 // Best thing to do in here is to fallback to the default color.
651 if (hsl[1] < 0.2f) {
652 contrastedColor = Notification.COLOR_DEFAULT;
653 }
654 contrastedColor = NotificationColorUtil.resolveContrastColor(mContext,
655 contrastedColor, mCachedContrastBackgroundColor);
656 }
657 mContrastedDrawableColor = contrastedColor;
658 }
659
Selim Cinek5b5beb012016-11-08 18:11:58 -0800660 public void setVisibleState(int state) {
661 setVisibleState(state, true /* animate */, null /* endRunnable */);
662 }
663
664 public void setVisibleState(int state, boolean animate) {
665 setVisibleState(state, animate, null);
666 }
667
668 @Override
669 public boolean hasOverlappingRendering() {
670 return false;
671 }
672
673 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700674 setVisibleState(visibleState, animate, endRunnable, 0);
675 }
676
677 /**
678 * Set the visibleState of this view.
679 *
680 * @param visibleState The new state.
681 * @param animate Should we animate?
682 * @param endRunnable The runnable to run at the end.
683 * @param duration The duration of an animation or 0 if the default should be taken.
684 */
685 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable,
686 long duration) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800687 boolean runnableAdded = false;
Selim Cinek49014f82016-11-04 14:55:30 -0700688 if (visibleState != mVisibleState) {
689 mVisibleState = visibleState;
Selim Cinek5ece5a72017-02-15 11:47:42 -0800690 if (mIconAppearAnimator != null) {
691 mIconAppearAnimator.cancel();
692 }
693 if (mDotAnimator != null) {
694 mDotAnimator.cancel();
695 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800696 if (animate) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800697 float targetAmount = 0.0f;
698 Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
699 if (visibleState == STATE_ICON) {
700 targetAmount = 1.0f;
701 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
702 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800703 float currentAmount = getIconAppearAmount();
704 if (targetAmount != currentAmount) {
705 mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
706 currentAmount, targetAmount);
707 mIconAppearAnimator.setInterpolator(interpolator);
Selim Cinekd03518c2018-03-15 12:13:51 -0700708 mIconAppearAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
709 : duration);
Selim Cinek65d418e2016-11-29 15:42:34 -0800710 mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
711 @Override
712 public void onAnimationEnd(Animator animation) {
713 mIconAppearAnimator = null;
714 runRunnable(endRunnable);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800715 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800716 });
717 mIconAppearAnimator.start();
718 runnableAdded = true;
719 }
Selim Cinek49014f82016-11-04 14:55:30 -0700720
Selim Cinek5b5beb012016-11-08 18:11:58 -0800721 targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
722 interpolator = Interpolators.FAST_OUT_LINEAR_IN;
723 if (visibleState == STATE_DOT) {
724 targetAmount = 1.0f;
725 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
726 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800727 currentAmount = getDotAppearAmount();
728 if (targetAmount != currentAmount) {
729 mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
730 currentAmount, targetAmount);
Selim Cinekd03518c2018-03-15 12:13:51 -0700731 mDotAnimator.setInterpolator(interpolator);;
732 mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
733 : duration);
Selim Cinek65d418e2016-11-29 15:42:34 -0800734 final boolean runRunnable = !runnableAdded;
735 mDotAnimator.addListener(new AnimatorListenerAdapter() {
736 @Override
737 public void onAnimationEnd(Animator animation) {
738 mDotAnimator = null;
739 if (runRunnable) {
740 runRunnable(endRunnable);
741 }
742 }
743 });
744 mDotAnimator.start();
745 runnableAdded = true;
746 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800747 } else {
748 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
Selim Cinek01a73f92016-12-06 16:13:42 -0800749 setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
750 : visibleState == STATE_ICON ? 2.0f
751 : 0.0f);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800752 }
Selim Cinek49014f82016-11-04 14:55:30 -0700753 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800754 if (!runnableAdded) {
755 runRunnable(endRunnable);
756 }
757 }
758
759 private void runRunnable(Runnable runnable) {
760 if (runnable != null) {
761 runnable.run();
762 }
Selim Cinek49014f82016-11-04 14:55:30 -0700763 }
764
Selim Cinek5b5beb012016-11-08 18:11:58 -0800765 public void setIconAppearAmount(float iconAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800766 if (mIconAppearAmount != iconAppearAmount) {
767 mIconAppearAmount = iconAppearAmount;
768 invalidate();
769 }
Selim Cinek49014f82016-11-04 14:55:30 -0700770 }
771
772 public float getIconAppearAmount() {
773 return mIconAppearAmount;
774 }
775
776 public int getVisibleState() {
777 return mVisibleState;
778 }
779
780 public void setDotAppearAmount(float dotAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800781 if (mDotAppearAmount != dotAppearAmount) {
782 mDotAppearAmount = dotAppearAmount;
783 invalidate();
784 }
Selim Cinek49014f82016-11-04 14:55:30 -0700785 }
786
Selim Cinek2b549f42016-11-22 16:38:51 -0800787 @Override
788 public void setVisibility(int visibility) {
789 super.setVisibility(visibility);
790 if (mOnVisibilityChangedListener != null) {
791 mOnVisibilityChangedListener.onVisibilityChanged(visibility);
792 }
793 }
794
Selim Cinek49014f82016-11-04 14:55:30 -0700795 public float getDotAppearAmount() {
796 return mDotAppearAmount;
797 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800798
799 public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
800 mOnVisibilityChangedListener = listener;
801 }
802
Adrian Roos456e0052017-04-04 16:44:29 -0700803 public void setDark(boolean dark, boolean fade, long delay) {
Adrian Roos456e0052017-04-04 16:44:29 -0700804 mDozer.setIntensityDark(f -> {
805 mDarkAmount = f;
Adrian Roos138f0342017-04-28 09:11:28 -0700806 updateIconScale();
Adrian Roos456e0052017-04-04 16:44:29 -0700807 updateDecorColor();
Adrian Roosf3eacd32017-04-14 16:35:58 -0700808 updateIconColor();
Adrian Roosfe0071f2017-07-06 18:45:10 +0200809 updateAllowAnimation();
Adrian Roos456e0052017-04-04 16:44:29 -0700810 }, dark, fade, delay);
811 }
812
Adrian Roosfe0071f2017-07-06 18:45:10 +0200813 private void updateAllowAnimation() {
814 if (mDarkAmount == 0 || mDarkAmount == 1) {
815 setAllowAnimation(mDarkAmount == 0);
816 }
817 }
818
Selim Cinek13859052017-10-05 17:12:18 -0700819 /**
820 * This method returns the drawing rect for the view which is different from the regular
821 * drawing rect, since we layout all children at position 0 and usually the translation is
822 * neglected. The standard implementation doesn't account for translation.
823 *
824 * @param outRect The (scrolled) drawing bounds of the view.
825 */
826 @Override
827 public void getDrawingRect(Rect outRect) {
828 super.getDrawingRect(outRect);
829 float translationX = getTranslationX();
830 float translationY = getTranslationY();
831 outRect.left += translationX;
832 outRect.right += translationX;
833 outRect.top += translationY;
834 outRect.bottom += translationY;
835 }
836
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700837 public void setIsInShelf(boolean isInShelf) {
838 mIsInShelf = isInShelf;
839 }
840
841 public boolean isInShelf() {
842 return mIsInShelf;
843 }
844
Selim Cinek887da3c2017-10-06 13:37:32 -0700845 @Override
846 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
847 super.onLayout(changed, left, top, right, bottom);
848 if (mLayoutRunnable != null) {
849 mLayoutRunnable.run();
850 mLayoutRunnable = null;
851 }
852 }
853
854 public void executeOnLayout(Runnable runnable) {
855 mLayoutRunnable = runnable;
856 }
857
Selim Cinekd03518c2018-03-15 12:13:51 -0700858 public void setDismissed() {
859 mDismissed = true;
860 if (mOnDismissListener != null) {
861 mOnDismissListener.run();
862 }
863 }
864
865 public boolean isDismissed() {
866 return mDismissed;
867 }
868
869 public void setOnDismissListener(Runnable onDismissListener) {
870 mOnDismissListener = onDismissListener;
871 }
872
Evan Lairde1d13c92018-03-20 16:58:01 -0400873 @Override
874 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
875 setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
876 }
877
878 @Override
879 public boolean isIconVisible() {
880 return mIcon != null && mIcon.visible;
881 }
882
883 @Override
884 public boolean isIconBlocked() {
885 return mBlocked;
886 }
887
Selim Cinek2b549f42016-11-22 16:38:51 -0800888 public interface OnVisibilityChangedListener {
889 void onVisibilityChanged(int newVisibility);
890 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700891}