blob: 5bb85e231f26d7161856f0160160b825b322b030 [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.ColorMatrixColorFilter;
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;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020057import java.util.Arrays;
John Spurlockde84f0e2013-06-12 12:41:00 -040058
Joe Onorato0cbda992010-05-02 16:28:15 -070059public class StatusBarIconView extends AnimatedImageView {
Selim Cinek875ba9b2017-02-13 16:20:17 -080060 public static final int NO_COLOR = 0;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +020061
62 /**
63 * Multiply alpha values with (1+DARK_ALPHA_BOOST) when dozing. The chosen value boosts
64 * everything above 30% to 50%, making it appear on 1bit color depths.
65 */
66 private static final float DARK_ALPHA_BOOST = 0.67f;
Selim Cinek875ba9b2017-02-13 16:20:17 -080067 private final int ANIMATION_DURATION_FAST = 100;
68
Selim Cinek49014f82016-11-04 14:55:30 -070069 public static final int STATE_ICON = 0;
70 public static final int STATE_DOT = 1;
71 public static final int STATE_HIDDEN = 2;
Joe Onorato0cbda992010-05-02 16:28:15 -070072
Selim Cinek49014f82016-11-04 14:55:30 -070073 private static final String TAG = "StatusBarIconView";
74 private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
75 = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
76
77 @Override
78 public void setValue(StatusBarIconView object, float value) {
79 object.setIconAppearAmount(value);
80 }
81
82 @Override
83 public Float get(StatusBarIconView object) {
84 return object.getIconAppearAmount();
85 }
86 };
Selim Cinek5b5beb012016-11-08 18:11:58 -080087 private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
Selim Cinek49014f82016-11-04 14:55:30 -070088 = new FloatProperty<StatusBarIconView>("dot_appear_amount") {
89
90 @Override
91 public void setValue(StatusBarIconView object, float value) {
92 object.setDotAppearAmount(value);
93 }
94
95 @Override
96 public Float get(StatusBarIconView object) {
97 return object.getDotAppearAmount();
98 }
99 };
100
101 private boolean mAlwaysScaleIcon;
Adrian Roos138f0342017-04-28 09:11:28 -0700102 private int mStatusBarIconDrawingSizeDark = 1;
103 private int mStatusBarIconDrawingSize = 1;
104 private int mStatusBarIconSize = 1;
Joe Onorato0cbda992010-05-02 16:28:15 -0700105 private StatusBarIcon mIcon;
106 @ViewDebug.ExportedProperty private String mSlot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400107 private Drawable mNumberBackground;
108 private Paint mNumberPain;
109 private int mNumberX;
110 private int mNumberY;
111 private String mNumberText;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700112 private StatusBarNotification mNotification;
Jason Monk3b230072015-05-29 11:11:29 -0400113 private final boolean mBlocked;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800114 private int mDensity;
Selim Cinek281c2022016-10-13 19:14:43 -0700115 private float mIconScale = 1.0f;
Selim Cinek49014f82016-11-04 14:55:30 -0700116 private final Paint mDotPaint = new Paint();
Selim Cinek49014f82016-11-04 14:55:30 -0700117 private float mDotRadius;
118 private int mStaticDotRadius;
119 private int mVisibleState = STATE_ICON;
120 private float mIconAppearAmount = 1.0f;
121 private ObjectAnimator mIconAppearAnimator;
122 private ObjectAnimator mDotAnimator;
123 private float mDotAppearAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800124 private OnVisibilityChangedListener mOnVisibilityChangedListener;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800125 private int mDrawableColor;
126 private int mIconColor;
Adrian Roos456e0052017-04-04 16:44:29 -0700127 private int mDecorColor;
128 private float mDarkAmount;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800129 private ValueAnimator mColorAnimator;
130 private int mCurrentSetColor = NO_COLOR;
131 private int mAnimationStartColor = NO_COLOR;
132 private final ValueAnimator.AnimatorUpdateListener mColorUpdater
133 = animation -> {
134 int newColor = NotificationUtils.interpolateColors(mAnimationStartColor, mIconColor,
135 animation.getAnimatedFraction());
136 setColorInternal(newColor);
137 };
Adrian Roos456e0052017-04-04 16:44:29 -0700138 private final NotificationIconDozeHelper mDozer;
Lucas Dupin83519da2017-06-21 11:58:31 -0700139 private int mContrastedDrawableColor;
140 private int mCachedContrastBackgroundColor = NO_COLOR;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200141 private float[] mMatrix;
142 private ColorMatrixColorFilter mMatrixColorFilter;
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700143 private boolean mIsInShelf;
Selim Cinek887da3c2017-10-06 13:37:32 -0700144 private Runnable mLayoutRunnable;
Selim Cinekd03518c2018-03-15 12:13:51 -0700145 private boolean mDismissed;
146 private Runnable mOnDismissListener;
Joe Onorato0cbda992010-05-02 16:28:15 -0700147
Selim Cinek72fc8db2017-06-06 18:07:47 -0700148 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
149 this(context, slot, sbn, false);
Jason Monk3b230072015-05-29 11:11:29 -0400150 }
151
Selim Cinek72fc8db2017-06-06 18:07:47 -0700152 public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
Jason Monk3b230072015-05-29 11:11:29 -0400153 boolean blocked) {
Joe Onorato0cbda992010-05-02 16:28:15 -0700154 super(context);
Adrian Roos456e0052017-04-04 16:44:29 -0700155 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400156 mBlocked = blocked;
Joe Onorato0cbda992010-05-02 16:28:15 -0700157 mSlot = slot;
Joe Onorato6c01a112010-10-04 17:38:47 -0400158 mNumberPain = new Paint();
159 mNumberPain.setTextAlign(Paint.Align.CENTER);
Alan Viverette4a357cd2015-03-18 18:37:18 -0700160 mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
Joe Onorato6c01a112010-10-04 17:38:47 -0400161 mNumberPain.setAntiAlias(true);
Selim Cinek72fc8db2017-06-06 18:07:47 -0700162 setNotification(sbn);
Adrian Roos138f0342017-04-28 09:11:28 -0700163 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800164 setScaleType(ScaleType.CENTER);
165 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Selim Cinek49014f82016-11-04 14:55:30 -0700166 if (mNotification != null) {
Selim Cinek875ba9b2017-02-13 16:20:17 -0800167 setDecorColor(getContext().getColor(
Selim Cinek49014f82016-11-04 14:55:30 -0700168 com.android.internal.R.color.notification_icon_default_color));
169 }
170 reloadDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800171 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400172
Adrian Roos138f0342017-04-28 09:11:28 -0700173 private void maybeUpdateIconScaleDimens() {
Daniel Sandler7579bca2011-08-18 15:47:26 -0400174 // We do not resize and scale system icons (on the right), only notification icons (on the
175 // left).
Selim Cinek3e7592d2016-04-11 09:35:54 +0800176 if (mNotification != null || mAlwaysScaleIcon) {
Adrian Roos138f0342017-04-28 09:11:28 -0700177 updateIconScaleDimens();
Daniel Sandler7579bca2011-08-18 15:47:26 -0400178 }
Selim Cinek3e7592d2016-04-11 09:35:54 +0800179 }
Daniel Sandlerabff0322011-09-27 11:19:34 -0400180
Adrian Roos138f0342017-04-28 09:11:28 -0700181 private void updateIconScaleDimens() {
Selim Cinek3e7592d2016-04-11 09:35:54 +0800182 Resources res = mContext.getResources();
Adrian Roos138f0342017-04-28 09:11:28 -0700183 mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
184 mStatusBarIconDrawingSizeDark =
185 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
186 mStatusBarIconDrawingSize =
187 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
188 updateIconScale();
189 }
190
191 private void updateIconScale() {
192 final float imageBounds = NotificationUtils.interpolate(
193 mStatusBarIconDrawingSize,
194 mStatusBarIconDrawingSizeDark,
195 mDarkAmount);
196 final int outerBounds = mStatusBarIconSize;
Selim Cinek281c2022016-10-13 19:14:43 -0700197 mIconScale = (float)imageBounds / (float)outerBounds;
Selim Cinek281c2022016-10-13 19:14:43 -0700198 }
199
Adrian Roosceac4a02017-05-30 20:25:52 -0700200 public float getIconScaleFullyDark() {
201 return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize;
202 }
203
Selim Cinek281c2022016-10-13 19:14:43 -0700204 public float getIconScale() {
205 return mIconScale;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800206 }
207
208 @Override
209 protected void onConfigurationChanged(Configuration newConfig) {
210 super.onConfigurationChanged(newConfig);
211 int density = newConfig.densityDpi;
212 if (density != mDensity) {
213 mDensity = density;
Adrian Roos138f0342017-04-28 09:11:28 -0700214 maybeUpdateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800215 updateDrawable();
Selim Cinek49014f82016-11-04 14:55:30 -0700216 reloadDimens();
217 }
218 }
219
220 private void reloadDimens() {
221 boolean applyRadius = mDotRadius == mStaticDotRadius;
222 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
223 if (applyRadius) {
224 mDotRadius = mStaticDotRadius;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800225 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700226 }
227
Selim Cinek72fc8db2017-06-06 18:07:47 -0700228 public void setNotification(StatusBarNotification notification) {
Christoph Studera0506e72014-07-31 20:27:39 +0200229 mNotification = notification;
Selim Cinek72fc8db2017-06-06 18:07:47 -0700230 if (notification != null) {
231 setContentDescription(notification.getNotification());
232 }
Christoph Studera0506e72014-07-31 20:27:39 +0200233 }
234
Daniel Sandler05e24142011-11-10 11:56:49 -0500235 public StatusBarIconView(Context context, AttributeSet attrs) {
236 super(context, attrs);
Adrian Roos456e0052017-04-04 16:44:29 -0700237 mDozer = new NotificationIconDozeHelper(context);
Jason Monk3b230072015-05-29 11:11:29 -0400238 mBlocked = false;
Selim Cinek3e7592d2016-04-11 09:35:54 +0800239 mAlwaysScaleIcon = true;
Adrian Roos138f0342017-04-28 09:11:28 -0700240 updateIconScaleDimens();
Selim Cinek3e7592d2016-04-11 09:35:54 +0800241 mDensity = context.getResources().getDisplayMetrics().densityDpi;
Daniel Sandler05e24142011-11-10 11:56:49 -0500242 }
243
Joe Onorato0cbda992010-05-02 16:28:15 -0700244 private static boolean streq(String a, String b) {
Joe Onorato66d7d012010-05-14 10:05:10 -0700245 if (a == b) {
246 return true;
247 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700248 if (a == null && b != null) {
249 return false;
250 }
251 if (a != null && b == null) {
252 return false;
253 }
254 return a.equals(b);
255 }
256
Dan Sandlerd63f9322015-05-06 15:18:49 -0400257 public boolean equalIcons(Icon a, Icon b) {
258 if (a == b) return true;
259 if (a.getType() != b.getType()) return false;
260 switch (a.getType()) {
261 case Icon.TYPE_RESOURCE:
262 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
263 case Icon.TYPE_URI:
264 return a.getUriString().equals(b.getUriString());
265 default:
266 return false;
267 }
268 }
Joe Onorato005847b2010-06-04 16:08:02 -0400269 /**
270 * Returns whether the set succeeded.
271 */
272 public boolean set(StatusBarIcon icon) {
Dan Sandlerd63f9322015-05-06 15:18:49 -0400273 final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
Joe Onorato005847b2010-06-04 16:08:02 -0400274 final boolean levelEquals = iconEquals
275 && mIcon.iconLevel == icon.iconLevel;
276 final boolean visibilityEquals = mIcon != null
277 && mIcon.visible == icon.visible;
Joe Onorato6c01a112010-10-04 17:38:47 -0400278 final boolean numberEquals = mIcon != null
279 && mIcon.number == icon.number;
280 mIcon = icon.clone();
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700281 setContentDescription(icon.contentDescription);
Joe Onorato005847b2010-06-04 16:08:02 -0400282 if (!iconEquals) {
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800283 if (!updateDrawable(false /* no clear */)) return false;
Selim Cinek2b6eb8032016-12-29 14:22:21 +0100284 // we have to clear the grayscale tag since it may have changed
285 setTag(R.id.icon_is_grayscale, null);
Joe Onorato0cbda992010-05-02 16:28:15 -0700286 }
Joe Onorato005847b2010-06-04 16:08:02 -0400287 if (!levelEquals) {
288 setImageLevel(icon.iconLevel);
Joe Onorato0cbda992010-05-02 16:28:15 -0700289 }
Daniel Sandler26c84b12011-07-27 00:09:40 -0400290
Joe Onorato6c01a112010-10-04 17:38:47 -0400291 if (!numberEquals) {
John Spurlock01534782014-01-13 11:59:22 -0500292 if (icon.number > 0 && getContext().getResources().getBoolean(
Joe Onorato8595a3d2010-11-19 18:12:07 -0800293 R.bool.config_statusBarShowNumber)) {
Joe Onorato6c01a112010-10-04 17:38:47 -0400294 if (mNumberBackground == null) {
295 mNumberBackground = getContext().getResources().getDrawable(
296 R.drawable.ic_notification_overlay);
297 }
298 placeNumber();
299 } else {
300 mNumberBackground = null;
301 mNumberText = null;
302 }
303 invalidate();
304 }
Joe Onorato005847b2010-06-04 16:08:02 -0400305 if (!visibilityEquals) {
Jason Monk3b230072015-05-29 11:11:29 -0400306 setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
Joe Onorato005847b2010-06-04 16:08:02 -0400307 }
Joe Onorato005847b2010-06-04 16:08:02 -0400308 return true;
Joe Onorato0cbda992010-05-02 16:28:15 -0700309 }
310
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800311 public void updateDrawable() {
312 updateDrawable(true /* with clear */);
313 }
314
315 private boolean updateDrawable(boolean withClear) {
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100316 if (mIcon == null) {
317 return false;
318 }
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800319 Drawable drawable;
320 try {
321 drawable = getIcon(mIcon);
322 } catch (OutOfMemoryError e) {
323 Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
324 return false;
325 }
326
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800327 if (drawable == null) {
Adrian Roosfb2d0cc2017-01-31 15:10:00 -0800328 Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
Fabrice Di Meglio82fca5d2013-01-09 15:42:31 -0800329 return false;
330 }
331 if (withClear) {
332 setImageDrawable(null);
333 }
334 setImageDrawable(drawable);
335 return true;
336 }
337
Selim Cinek72fc8db2017-06-06 18:07:47 -0700338 public Icon getSourceIcon() {
339 return mIcon.icon;
340 }
341
Joe Onoratof5510542010-06-01 07:46:41 -0700342 private Drawable getIcon(StatusBarIcon icon) {
343 return getIcon(getContext(), icon);
344 }
345
Joe Onorato0cbda992010-05-02 16:28:15 -0700346 /**
Dan Sandlerd63f9322015-05-06 15:18:49 -0400347 * Returns the right icon to use for this item
John Spurlock209bede2013-07-17 12:23:27 -0400348 *
Dan Sandlerd63f9322015-05-06 15:18:49 -0400349 * @param context Context to use to get resources
Joe Onorato0cbda992010-05-02 16:28:15 -0700350 * @return Drawable for this item, or null if the package or item could not
351 * be found
352 */
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800353 public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
354 int userId = statusBarIcon.user.getIdentifier();
Dan Sandlerd63f9322015-05-06 15:18:49 -0400355 if (userId == UserHandle.USER_ALL) {
Xiaohui Chen87d0e252015-07-30 15:38:16 -0700356 userId = UserHandle.USER_SYSTEM;
Joe Onorato0cbda992010-05-02 16:28:15 -0700357 }
Anthony Chen55e8e1e2016-01-08 10:31:46 -0800358
359 Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
360
361 TypedValue typedValue = new TypedValue();
362 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
363 float scaleFactor = typedValue.getFloat();
364
365 // No need to scale the icon, so return it as is.
366 if (scaleFactor == 1.f) {
367 return icon;
368 }
369
370 return new ScalingDrawableWrapper(icon, scaleFactor);
Joe Onorato0cbda992010-05-02 16:28:15 -0700371 }
Joe Onoratob77f53b2010-05-28 19:59:51 -0400372
373 public StatusBarIcon getStatusBarIcon() {
374 return mIcon;
375 }
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700376
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700377 @Override
378 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
379 super.onInitializeAccessibilityEvent(event);
380 if (mNotification != null) {
Selim Cinek72fc8db2017-06-06 18:07:47 -0700381 event.setParcelableData(mNotification.getNotification());
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700382 }
383 }
384
385 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400386 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
387 super.onSizeChanged(w, h, oldw, oldh);
388 if (mNumberBackground != null) {
389 placeNumber();
390 }
391 }
392
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700393 @Override
Jorim Jaggi66ac1332015-01-21 19:22:26 +0100394 public void onRtlPropertiesChanged(int layoutDirection) {
395 super.onRtlPropertiesChanged(layoutDirection);
396 updateDrawable();
397 }
398
399 @Override
Joe Onorato6c01a112010-10-04 17:38:47 -0400400 protected void onDraw(Canvas canvas) {
Selim Cinek49014f82016-11-04 14:55:30 -0700401 if (mIconAppearAmount > 0.0f) {
402 canvas.save();
403 canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
404 getWidth() / 2, getHeight() / 2);
405 super.onDraw(canvas);
406 canvas.restore();
407 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400408
409 if (mNumberBackground != null) {
410 mNumberBackground.draw(canvas);
411 canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
412 }
Selim Cinek49014f82016-11-04 14:55:30 -0700413 if (mDotAppearAmount != 0.0f) {
414 float radius;
415 float alpha;
416 if (mDotAppearAmount <= 1.0f) {
417 radius = mDotRadius * mDotAppearAmount;
418 alpha = 1.0f;
419 } else {
420 float fadeOutAmount = mDotAppearAmount - 1.0f;
421 alpha = 1.0f - fadeOutAmount;
422 radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
423 }
424 mDotPaint.setAlpha((int) (alpha * 255));
425 canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
426 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400427 }
428
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700429 @Override
Joe Onoratof9ec03c2010-09-23 15:09:18 -0700430 protected void debug(int depth) {
431 super.debug(depth);
432 Log.d("View", debugIndent(depth) + "slot=" + mSlot);
433 Log.d("View", debugIndent(depth) + "icon=" + mIcon);
434 }
Joe Onorato6c01a112010-10-04 17:38:47 -0400435
436 void placeNumber() {
Daniel Sandlerebce0112011-06-16 16:44:51 -0400437 final String str;
John Spurlock01534782014-01-13 11:59:22 -0500438 final int tooBig = getContext().getResources().getInteger(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400439 android.R.integer.status_bar_notification_info_maxnum);
440 if (mIcon.number > tooBig) {
John Spurlock01534782014-01-13 11:59:22 -0500441 str = getContext().getResources().getString(
Daniel Sandlerebce0112011-06-16 16:44:51 -0400442 android.R.string.status_bar_notification_info_overflow);
443 } else {
444 NumberFormat f = NumberFormat.getIntegerInstance();
445 str = f.format(mIcon.number);
446 }
447 mNumberText = str;
448
Joe Onorato6c01a112010-10-04 17:38:47 -0400449 final int w = getWidth();
450 final int h = getHeight();
451 final Rect r = new Rect();
452 mNumberPain.getTextBounds(str, 0, str.length(), r);
453 final int tw = r.right - r.left;
454 final int th = r.bottom - r.top;
455 mNumberBackground.getPadding(r);
456 int dw = r.left + tw + r.right;
457 if (dw < mNumberBackground.getMinimumWidth()) {
458 dw = mNumberBackground.getMinimumWidth();
459 }
460 mNumberX = w-r.right-((dw-r.right-r.left)/2);
461 int dh = r.top + th + r.bottom;
462 if (dh < mNumberBackground.getMinimumWidth()) {
463 dh = mNumberBackground.getMinimumWidth();
464 }
465 mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
466 mNumberBackground.setBounds(w-dw, h-dh, w, h);
467 }
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700468
469 private void setContentDescription(Notification notification) {
470 if (notification != null) {
Adrian Rooseba05822016-04-22 17:09:27 -0700471 String d = contentDescForNotification(mContext, notification);
472 if (!TextUtils.isEmpty(d)) {
473 setContentDescription(d);
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700474 }
475 }
476 }
Daniel Sandler7579bca2011-08-18 15:47:26 -0400477
478 public String toString() {
John Spurlock209bede2013-07-17 12:23:27 -0400479 return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
Daniel Sandler7579bca2011-08-18 15:47:26 -0400480 + " notification=" + mNotification + ")";
481 }
Jason Monk3b230072015-05-29 11:11:29 -0400482
Selim Cinek72fc8db2017-06-06 18:07:47 -0700483 public StatusBarNotification getNotification() {
484 return mNotification;
485 }
486
Jason Monk3b230072015-05-29 11:11:29 -0400487 public String getSlot() {
488 return mSlot;
489 }
Adrian Rooseba05822016-04-22 17:09:27 -0700490
491
492 public static String contentDescForNotification(Context c, Notification n) {
Adrian Roosa8e18ef2016-05-26 17:17:02 -0700493 String appName = "";
494 try {
495 Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
496 appName = builder.loadHeaderAppName();
497 } catch (RuntimeException e) {
498 Log.e(TAG, "Unable to recover builder", e);
499 // Trying to get the app name from the app info instead.
500 Parcelable appInfo = n.extras.getParcelable(
501 Notification.EXTRA_BUILDER_APPLICATION_INFO);
502 if (appInfo instanceof ApplicationInfo) {
503 appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel(
504 c.getPackageManager()));
505 }
506 }
Adrian Roos51548e62016-04-28 11:20:26 -0700507
Julia Reynolds7f9ce782016-05-17 15:34:34 -0400508 CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
Adrian Roosf76600e2017-04-19 15:32:43 -0700509 CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
Adrian Rooseba05822016-04-22 17:09:27 -0700510 CharSequence ticker = n.tickerText;
Adrian Roos51548e62016-04-28 11:20:26 -0700511
Adrian Roosf76600e2017-04-19 15:32:43 -0700512 // Some apps just put the app name into the title
513 CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
514
515 CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
Adrian Roos6e782a52017-03-30 18:34:10 -0700516 : !TextUtils.isEmpty(ticker) ? ticker : "";
Adrian Roos51548e62016-04-28 11:20:26 -0700517
518 return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
Adrian Rooseba05822016-04-22 17:09:27 -0700519 }
Adrian Roos51548e62016-04-28 11:20:26 -0700520
Selim Cinek875ba9b2017-02-13 16:20:17 -0800521 /**
522 * Set the color that is used to draw decoration like the overflow dot. This will not be applied
523 * to the drawable.
524 */
525 public void setDecorColor(int iconTint) {
Adrian Roos456e0052017-04-04 16:44:29 -0700526 mDecorColor = iconTint;
527 updateDecorColor();
528 }
529
530 private void updateDecorColor() {
531 int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
532 if (mDotPaint.getColor() != color) {
533 mDotPaint.setColor(color);
534
535 if (mDotAppearAmount != 0) {
536 invalidate();
537 }
538 }
Selim Cinek49014f82016-11-04 14:55:30 -0700539 }
540
Selim Cinek875ba9b2017-02-13 16:20:17 -0800541 /**
542 * Set the static color that should be used for the drawable of this icon if it's not
543 * transitioning this also immediately sets the color.
544 */
545 public void setStaticDrawableColor(int color) {
546 mDrawableColor = color;
547 setColorInternal(color);
Lucas Dupin83519da2017-06-21 11:58:31 -0700548 updateContrastedStaticColor();
Selim Cinek875ba9b2017-02-13 16:20:17 -0800549 mIconColor = color;
Adrian Roos456e0052017-04-04 16:44:29 -0700550 mDozer.setColor(color);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800551 }
552
553 private void setColorInternal(int color) {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700554 mCurrentSetColor = color;
555 updateIconColor();
556 }
557
558 private void updateIconColor() {
559 if (mCurrentSetColor != NO_COLOR) {
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200560 if (mMatrixColorFilter == null) {
561 mMatrix = new float[4 * 5];
562 mMatrixColorFilter = new ColorMatrixColorFilter(mMatrix);
563 }
564 int color = NotificationUtils.interpolateColors(
565 mCurrentSetColor, Color.WHITE, mDarkAmount);
566 updateTintMatrix(mMatrix, color, DARK_ALPHA_BOOST * mDarkAmount);
567 mMatrixColorFilter.setColorMatrixArray(mMatrix);
568 setColorFilter(mMatrixColorFilter);
569 invalidate(); // setColorFilter only invalidates if the filter instance changed.
Selim Cinek875ba9b2017-02-13 16:20:17 -0800570 } else {
Adrian Roosf3eacd32017-04-14 16:35:58 -0700571 mDozer.updateGrayscale(this, mDarkAmount);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800572 }
Selim Cinek875ba9b2017-02-13 16:20:17 -0800573 }
574
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200575 /**
576 * Updates {@param array} such that it represents a matrix that changes RGB to {@param color}
Adrian Roos24dbff32017-08-02 16:57:11 +0200577 * and multiplies the alpha channel with the color's alpha+{@param alphaBoost}.
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200578 */
579 private static void updateTintMatrix(float[] array, int color, float alphaBoost) {
580 Arrays.fill(array, 0);
581 array[4] = Color.red(color);
582 array[9] = Color.green(color);
583 array[14] = Color.blue(color);
Adrian Roos24dbff32017-08-02 16:57:11 +0200584 array[18] = Color.alpha(color) / 255f + alphaBoost;
Adrian Roosc3e8cfc2017-07-17 17:09:39 +0200585 }
586
Selim Cinek875ba9b2017-02-13 16:20:17 -0800587 public void setIconColor(int iconColor, boolean animate) {
588 if (mIconColor != iconColor) {
589 mIconColor = iconColor;
590 if (mColorAnimator != null) {
591 mColorAnimator.cancel();
592 }
593 if (mCurrentSetColor == iconColor) {
594 return;
595 }
596 if (animate && mCurrentSetColor != NO_COLOR) {
597 mAnimationStartColor = mCurrentSetColor;
598 mColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
599 mColorAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
600 mColorAnimator.setDuration(ANIMATION_DURATION_FAST);
601 mColorAnimator.addUpdateListener(mColorUpdater);
602 mColorAnimator.addListener(new AnimatorListenerAdapter() {
603 @Override
604 public void onAnimationEnd(Animator animation) {
605 mColorAnimator = null;
606 mAnimationStartColor = NO_COLOR;
607 }
608 });
609 mColorAnimator.start();
610 } else {
611 setColorInternal(iconColor);
612 }
613 }
614 }
615
616 public int getStaticDrawableColor() {
617 return mDrawableColor;
618 }
619
Lucas Dupin83519da2017-06-21 11:58:31 -0700620 /**
621 * A drawable color that passes GAR on a specific background.
622 * This value is cached.
623 *
624 * @param backgroundColor Background to test against.
625 * @return GAR safe version of {@link StatusBarIconView#getStaticDrawableColor()}.
626 */
627 int getContrastedStaticDrawableColor(int backgroundColor) {
628 if (mCachedContrastBackgroundColor != backgroundColor) {
629 mCachedContrastBackgroundColor = backgroundColor;
630 updateContrastedStaticColor();
631 }
632 return mContrastedDrawableColor;
633 }
634
635 private void updateContrastedStaticColor() {
Lucas Dupinddbaf842017-06-23 09:30:49 -0700636 if (Color.alpha(mCachedContrastBackgroundColor) != 255) {
637 mContrastedDrawableColor = mDrawableColor;
Lucas Dupin83519da2017-06-21 11:58:31 -0700638 return;
639 }
640 // We'll modify the color if it doesn't pass GAR
641 int contrastedColor = mDrawableColor;
642 if (!NotificationColorUtil.satisfiesTextContrast(mCachedContrastBackgroundColor,
643 contrastedColor)) {
644 float[] hsl = new float[3];
645 ColorUtils.colorToHSL(mDrawableColor, hsl);
646 // This is basically a light grey, pushing the color will only distort it.
647 // Best thing to do in here is to fallback to the default color.
648 if (hsl[1] < 0.2f) {
649 contrastedColor = Notification.COLOR_DEFAULT;
650 }
651 contrastedColor = NotificationColorUtil.resolveContrastColor(mContext,
652 contrastedColor, mCachedContrastBackgroundColor);
653 }
654 mContrastedDrawableColor = contrastedColor;
655 }
656
Selim Cinek5b5beb012016-11-08 18:11:58 -0800657 public void setVisibleState(int state) {
658 setVisibleState(state, true /* animate */, null /* endRunnable */);
659 }
660
661 public void setVisibleState(int state, boolean animate) {
662 setVisibleState(state, animate, null);
663 }
664
665 @Override
666 public boolean hasOverlappingRendering() {
667 return false;
668 }
669
670 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700671 setVisibleState(visibleState, animate, endRunnable, 0);
672 }
673
674 /**
675 * Set the visibleState of this view.
676 *
677 * @param visibleState The new state.
678 * @param animate Should we animate?
679 * @param endRunnable The runnable to run at the end.
680 * @param duration The duration of an animation or 0 if the default should be taken.
681 */
682 public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable,
683 long duration) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800684 boolean runnableAdded = false;
Selim Cinek49014f82016-11-04 14:55:30 -0700685 if (visibleState != mVisibleState) {
686 mVisibleState = visibleState;
Selim Cinek5ece5a72017-02-15 11:47:42 -0800687 if (mIconAppearAnimator != null) {
688 mIconAppearAnimator.cancel();
689 }
690 if (mDotAnimator != null) {
691 mDotAnimator.cancel();
692 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800693 if (animate) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800694 float targetAmount = 0.0f;
695 Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
696 if (visibleState == STATE_ICON) {
697 targetAmount = 1.0f;
698 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
699 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800700 float currentAmount = getIconAppearAmount();
701 if (targetAmount != currentAmount) {
702 mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
703 currentAmount, targetAmount);
704 mIconAppearAnimator.setInterpolator(interpolator);
Selim Cinekd03518c2018-03-15 12:13:51 -0700705 mIconAppearAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
706 : duration);
Selim Cinek65d418e2016-11-29 15:42:34 -0800707 mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
708 @Override
709 public void onAnimationEnd(Animator animation) {
710 mIconAppearAnimator = null;
711 runRunnable(endRunnable);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800712 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800713 });
714 mIconAppearAnimator.start();
715 runnableAdded = true;
716 }
Selim Cinek49014f82016-11-04 14:55:30 -0700717
Selim Cinek5b5beb012016-11-08 18:11:58 -0800718 targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
719 interpolator = Interpolators.FAST_OUT_LINEAR_IN;
720 if (visibleState == STATE_DOT) {
721 targetAmount = 1.0f;
722 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
723 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800724 currentAmount = getDotAppearAmount();
725 if (targetAmount != currentAmount) {
726 mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
727 currentAmount, targetAmount);
Selim Cinekd03518c2018-03-15 12:13:51 -0700728 mDotAnimator.setInterpolator(interpolator);;
729 mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
730 : duration);
Selim Cinek65d418e2016-11-29 15:42:34 -0800731 final boolean runRunnable = !runnableAdded;
732 mDotAnimator.addListener(new AnimatorListenerAdapter() {
733 @Override
734 public void onAnimationEnd(Animator animation) {
735 mDotAnimator = null;
736 if (runRunnable) {
737 runRunnable(endRunnable);
738 }
739 }
740 });
741 mDotAnimator.start();
742 runnableAdded = true;
743 }
Selim Cinek5b5beb012016-11-08 18:11:58 -0800744 } else {
745 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
Selim Cinek01a73f92016-12-06 16:13:42 -0800746 setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
747 : visibleState == STATE_ICON ? 2.0f
748 : 0.0f);
Selim Cinek5b5beb012016-11-08 18:11:58 -0800749 }
Selim Cinek49014f82016-11-04 14:55:30 -0700750 }
Selim Cinek65d418e2016-11-29 15:42:34 -0800751 if (!runnableAdded) {
752 runRunnable(endRunnable);
753 }
754 }
755
756 private void runRunnable(Runnable runnable) {
757 if (runnable != null) {
758 runnable.run();
759 }
Selim Cinek49014f82016-11-04 14:55:30 -0700760 }
761
Selim Cinek5b5beb012016-11-08 18:11:58 -0800762 public void setIconAppearAmount(float iconAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800763 if (mIconAppearAmount != iconAppearAmount) {
764 mIconAppearAmount = iconAppearAmount;
765 invalidate();
766 }
Selim Cinek49014f82016-11-04 14:55:30 -0700767 }
768
769 public float getIconAppearAmount() {
770 return mIconAppearAmount;
771 }
772
773 public int getVisibleState() {
774 return mVisibleState;
775 }
776
777 public void setDotAppearAmount(float dotAppearAmount) {
Selim Cinek9ef119c2017-03-01 15:13:36 -0800778 if (mDotAppearAmount != dotAppearAmount) {
779 mDotAppearAmount = dotAppearAmount;
780 invalidate();
781 }
Selim Cinek49014f82016-11-04 14:55:30 -0700782 }
783
Selim Cinek2b549f42016-11-22 16:38:51 -0800784 @Override
785 public void setVisibility(int visibility) {
786 super.setVisibility(visibility);
787 if (mOnVisibilityChangedListener != null) {
788 mOnVisibilityChangedListener.onVisibilityChanged(visibility);
789 }
790 }
791
Selim Cinek49014f82016-11-04 14:55:30 -0700792 public float getDotAppearAmount() {
793 return mDotAppearAmount;
794 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800795
796 public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
797 mOnVisibilityChangedListener = listener;
798 }
799
Adrian Roos456e0052017-04-04 16:44:29 -0700800 public void setDark(boolean dark, boolean fade, long delay) {
Adrian Roos456e0052017-04-04 16:44:29 -0700801 mDozer.setIntensityDark(f -> {
802 mDarkAmount = f;
Adrian Roos138f0342017-04-28 09:11:28 -0700803 updateIconScale();
Adrian Roos456e0052017-04-04 16:44:29 -0700804 updateDecorColor();
Adrian Roosf3eacd32017-04-14 16:35:58 -0700805 updateIconColor();
Adrian Roosfe0071f2017-07-06 18:45:10 +0200806 updateAllowAnimation();
Adrian Roos456e0052017-04-04 16:44:29 -0700807 }, dark, fade, delay);
808 }
809
Adrian Roosfe0071f2017-07-06 18:45:10 +0200810 private void updateAllowAnimation() {
811 if (mDarkAmount == 0 || mDarkAmount == 1) {
812 setAllowAnimation(mDarkAmount == 0);
813 }
814 }
815
Selim Cinek13859052017-10-05 17:12:18 -0700816 /**
817 * This method returns the drawing rect for the view which is different from the regular
818 * drawing rect, since we layout all children at position 0 and usually the translation is
819 * neglected. The standard implementation doesn't account for translation.
820 *
821 * @param outRect The (scrolled) drawing bounds of the view.
822 */
823 @Override
824 public void getDrawingRect(Rect outRect) {
825 super.getDrawingRect(outRect);
826 float translationX = getTranslationX();
827 float translationY = getTranslationY();
828 outRect.left += translationX;
829 outRect.right += translationX;
830 outRect.top += translationY;
831 outRect.bottom += translationY;
832 }
833
Selim Cinekd95ca7c2017-07-26 12:20:38 -0700834 public void setIsInShelf(boolean isInShelf) {
835 mIsInShelf = isInShelf;
836 }
837
838 public boolean isInShelf() {
839 return mIsInShelf;
840 }
841
Selim Cinek887da3c2017-10-06 13:37:32 -0700842 @Override
843 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
844 super.onLayout(changed, left, top, right, bottom);
845 if (mLayoutRunnable != null) {
846 mLayoutRunnable.run();
847 mLayoutRunnable = null;
848 }
849 }
850
851 public void executeOnLayout(Runnable runnable) {
852 mLayoutRunnable = runnable;
853 }
854
Selim Cinekd03518c2018-03-15 12:13:51 -0700855 public void setDismissed() {
856 mDismissed = true;
857 if (mOnDismissListener != null) {
858 mOnDismissListener.run();
859 }
860 }
861
862 public boolean isDismissed() {
863 return mDismissed;
864 }
865
866 public void setOnDismissListener(Runnable onDismissListener) {
867 mOnDismissListener = onDismissListener;
868 }
869
Selim Cinek2b549f42016-11-22 16:38:51 -0800870 public interface OnVisibilityChangedListener {
871 void onVisibilityChanged(int newVisibility);
872 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700873}