blob: 6f1ed28d649e100b53e00e8149e16df3c99be245 [file] [log] [blame]
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001/*
Mady Mellor3f2efdb2018-11-21 11:30:45 -08002 * Copyright (C) 2018 The Android Open Source Project
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08003 *
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
17package com.android.systemui.bubbles;
18
Mady Mellor3f2efdb2018-11-21 11:30:45 -080019import android.annotation.Nullable;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080020import android.app.Notification;
21import android.content.Context;
22import android.graphics.Color;
Lyn Han56a3ec52019-03-25 15:04:21 -070023import android.graphics.drawable.AdaptiveIconDrawable;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080024import android.graphics.drawable.ColorDrawable;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080025import android.graphics.drawable.Drawable;
26import android.graphics.drawable.Icon;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080027import android.graphics.drawable.InsetDrawable;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080028import android.util.AttributeSet;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080029import android.widget.FrameLayout;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080030
Mady Mellor3f2efdb2018-11-21 11:30:45 -080031import com.android.internal.graphics.ColorUtils;
32import com.android.systemui.Interpolators;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080033import com.android.systemui.R;
Ned Burnsf81c4c42019-01-07 14:10:43 -050034import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080035import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
36
37/**
Mady Mellor3f2efdb2018-11-21 11:30:45 -080038 * A floating object on the screen that can post message updates.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080039 */
Joshua Tsuji442b6272019-02-08 13:23:43 -050040public class BubbleView extends FrameLayout {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080041 private static final String TAG = "BubbleView";
42
Lyn Han56a3ec52019-03-25 15:04:21 -070043 private static final int DARK_ICON_ALPHA = 180;
44 private static final double ICON_MIN_CONTRAST = 4.1;
45 private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080046 // Same value as Launcher3 badge code
47 private static final float WHITE_SCRIM_ALPHA = 0.54f;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080048 private Context mContext;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080049
50 private BadgedImageView mBadgedImageView;
Joshua Tsuji6549e702019-05-02 13:13:16 -040051 private int mBadgeColor;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080052 private int mPadding;
53 private int mIconInset;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080054
Joshua Tsuji6549e702019-05-02 13:13:16 -040055 private boolean mSuppressDot = false;
56
Ned Burnsf81c4c42019-01-07 14:10:43 -050057 private NotificationEntry mEntry;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080058
59 public BubbleView(Context context) {
60 this(context, null);
61 }
62
63 public BubbleView(Context context, AttributeSet attrs) {
64 this(context, attrs, 0);
65 }
66
67 public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
68 this(context, attrs, defStyleAttr, 0);
69 }
70
71 public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
72 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080073 mContext = context;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080074 // XXX: can this padding just be on the view and we look it up?
75 mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
76 mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
77 }
78
79 @Override
80 protected void onFinishInflate() {
81 super.onFinishInflate();
Joshua Tsuji614b1df2019-03-26 13:57:05 -040082 mBadgedImageView = findViewById(R.id.bubble_image);
Mady Mellor3f2efdb2018-11-21 11:30:45 -080083 }
84
85 @Override
86 protected void onAttachedToWindow() {
87 super.onAttachedToWindow();
Mady Mellor3f2efdb2018-11-21 11:30:45 -080088 }
89
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080090 /**
91 * Populates this view with a notification.
Mady Mellor3f2efdb2018-11-21 11:30:45 -080092 * <p>
93 * This should only be called when a new notification is being set on the view, updates to the
94 * current notification should use {@link #update(NotificationEntry)}.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080095 *
96 * @param entry the notification to display as a bubble.
97 */
Ned Burnsf81c4c42019-01-07 14:10:43 -050098 public void setNotif(NotificationEntry entry) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080099 mEntry = entry;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800100 updateViews();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800101 }
102
103 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800104 * The {@link NotificationEntry} associated with this view, if one exists.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800105 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800106 @Nullable
Ned Burnsf81c4c42019-01-07 14:10:43 -0500107 public NotificationEntry getEntry() {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800108 return mEntry;
109 }
110
111 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800112 * The key for the {@link NotificationEntry} associated with this view, if one exists.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800113 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800114 @Nullable
115 public String getKey() {
116 return (mEntry != null) ? mEntry.key : null;
117 }
118
119 /**
120 * Updates the UI based on the entry, updates badge and animates messages as needed.
121 */
122 public void update(NotificationEntry entry) {
123 mEntry = entry;
124 updateViews();
125 }
126
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800127 /**
128 * @return the {@link ExpandableNotificationRow} view to display notification content when the
129 * bubble is expanded.
130 */
131 @Nullable
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800132 public ExpandableNotificationRow getRowView() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800133 return (mEntry != null) ? mEntry.getRow() : null;
134 }
135
Joshua Tsuji6549e702019-05-02 13:13:16 -0400136 /** Changes the dot's visibility to match the bubble view's state. */
137 void updateDotVisibility(boolean animate) {
138 updateDotVisibility(animate, null /* after */);
139 }
140
Joshua Tsuji6549e702019-05-02 13:13:16 -0400141
142 /**
143 * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
144 * flyout is visible or animating, to hide the dot until the flyout visually transforms into it.
145 */
146 void setSuppressDot(boolean suppressDot, boolean animate) {
147 mSuppressDot = suppressDot;
148 updateDotVisibility(animate);
149 }
150
151 /** Sets the position of the 'new' dot, animating it out and back in if requested. */
152 void setDotPosition(boolean onLeft, boolean animate) {
153 if (animate && onLeft != mBadgedImageView.getDotPosition() && !mSuppressDot) {
154 animateDot(false /* showDot */, () -> {
155 mBadgedImageView.setDotPosition(onLeft);
156 animateDot(true /* showDot */, null);
157 });
158 } else {
159 mBadgedImageView.setDotPosition(onLeft);
160 }
161 }
162
163 boolean getDotPositionOnLeft() {
164 return mBadgedImageView.getDotPosition();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800165 }
166
167 /**
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400168 * Changes the dot's visibility to match the bubble view's state, running the provided callback
169 * after animation if requested.
170 */
171 private void updateDotVisibility(boolean animate, Runnable after) {
172 boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
173
174 if (animate) {
175 animateDot(showDot, after);
176 } else {
177 mBadgedImageView.setShowDot(showDot);
178 }
179 }
180
181 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800182 * Animates the badge to show or hide.
183 */
Joshua Tsuji6549e702019-05-02 13:13:16 -0400184 private void animateDot(boolean showDot, Runnable after) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800185 if (mBadgedImageView.isShowingDot() != showDot) {
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400186 if (showDot) {
187 mBadgedImageView.setShowDot(true);
188 }
189
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800190 mBadgedImageView.clearAnimation();
191 mBadgedImageView.animate().setDuration(200)
192 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
193 .setUpdateListener((valueAnimator) -> {
194 float fraction = valueAnimator.getAnimatedFraction();
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400195 fraction = showDot ? fraction : 1f - fraction;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800196 mBadgedImageView.setDotScale(fraction);
197 }).withEndAction(() -> {
Joshua Tsuji6549e702019-05-02 13:13:16 -0400198 if (!showDot) {
199 mBadgedImageView.setShowDot(false);
200 }
201
202 if (after != null) {
203 after.run();
204 }
Mark Renouf8eafa222019-01-23 17:01:55 -0500205 }).start();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800206 }
207 }
208
Lyn Han80b80112019-04-04 14:03:40 -0700209 void updateViews() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800210 if (mEntry == null) {
211 return;
212 }
Mady Mellor9848a6c2019-03-19 15:29:05 -0700213 Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800214 Notification n = mEntry.notification.getNotification();
Mady Mellor9848a6c2019-03-19 15:29:05 -0700215 Icon ic;
216 boolean needsTint;
217 if (metadata != null) {
218 ic = metadata.getIcon();
219 needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
220 } else {
221 needsTint = n.getLargeIcon() == null;
222 ic = needsTint ? n.getSmallIcon() : n.getLargeIcon();
223 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800224 Drawable iconDrawable = ic.loadDrawable(mContext);
Mady Mellor9848a6c2019-03-19 15:29:05 -0700225 if (needsTint) {
Lyn Han56a3ec52019-03-25 15:04:21 -0700226 mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800227 } else {
228 mBadgedImageView.setImageDrawable(iconDrawable);
229 }
230 int badgeColor = determineDominateColor(iconDrawable, n.color);
Joshua Tsuji6549e702019-05-02 13:13:16 -0400231 mBadgeColor = badgeColor;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800232 mBadgedImageView.setDotColor(badgeColor);
Joshua Tsuji6549e702019-05-02 13:13:16 -0400233 animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
234 }
235
236 int getBadgeColor() {
237 return mBadgeColor;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800238 }
239
Lyn Han56a3ec52019-03-25 15:04:21 -0700240 private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
Lyn Han80b80112019-04-04 14:03:40 -0700241 iconDrawable = checkTint(iconDrawable, backgroundColor);
242 InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
243 ColorDrawable background = new ColorDrawable(backgroundColor);
244 return new AdaptiveIconDrawable(background, foreground);
245 }
246
247 private Drawable checkTint(Drawable iconDrawable, int backgroundColor) {
Lyn Han56a3ec52019-03-25 15:04:21 -0700248 backgroundColor = ColorUtils.setAlphaComponent(backgroundColor, 255 /* alpha */);
249 if (backgroundColor == Color.TRANSPARENT) {
250 // ColorUtils throws exception when background is translucent.
251 backgroundColor = DEFAULT_BACKGROUND_COLOR;
252 }
253 iconDrawable.setTint(Color.WHITE);
254 double contrastRatio = ColorUtils.calculateContrast(Color.WHITE, backgroundColor);
255 if (contrastRatio < ICON_MIN_CONTRAST) {
256 int dark = ColorUtils.setAlphaComponent(Color.BLACK, DARK_ICON_ALPHA);
257 iconDrawable.setTint(dark);
258 }
Lyn Han80b80112019-04-04 14:03:40 -0700259 return iconDrawable;
Lyn Han56a3ec52019-03-25 15:04:21 -0700260 }
261
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800262 private int determineDominateColor(Drawable d, int defaultTint) {
263 // XXX: should we pull from the drawable, app icon, notif tint?
264 return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800265 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800266}