blob: 3808702176a936023fe315ea6f2389fe65bf94cd [file] [log] [blame]
Jorim Jaggi4e857f42014-11-17 19:14:04 +01001/*
2 * Copyright (C) 2014 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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.row.wrapper;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010018
Selim Cinek6fe4a102019-01-25 14:38:24 -080019import android.annotation.ColorInt;
20import android.app.Notification;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010021import android.content.Context;
Selim Cinek6fe4a102019-01-25 14:38:24 -080022import android.content.res.Configuration;
Gus Prevas9cc96602018-10-11 11:24:23 -040023import android.graphics.Color;
Selim Cinek6fe4a102019-01-25 14:38:24 -080024import android.graphics.ColorMatrix;
25import android.graphics.ColorMatrixColorFilter;
26import android.graphics.Paint;
Selim Cinek7b9605b2017-01-19 17:36:00 -080027import android.graphics.drawable.ColorDrawable;
28import android.graphics.drawable.Drawable;
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -080029import android.os.Build;
Selim Cinekea4bef72015-12-02 15:51:10 -080030import android.view.NotificationHeaderView;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010031import android.view.View;
Selim Cinek6fe4a102019-01-25 14:38:24 -080032import android.view.ViewGroup;
33import android.widget.TextView;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010034
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -080035import com.android.internal.annotations.VisibleForTesting;
Selim Cinek6fe4a102019-01-25 14:38:24 -080036import com.android.internal.graphics.ColorUtils;
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -080037import com.android.internal.util.ContrastColorUtil;
Selim Cinek0ffbda62016-01-01 20:29:12 +010038import com.android.systemui.statusbar.CrossFadeHelper;
Gus Prevasab336792018-11-14 13:52:20 -050039import com.android.systemui.statusbar.TransformableView;
Rohan Shah20790b82018-07-02 17:21:04 -070040import com.android.systemui.statusbar.notification.TransformState;
41import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Selim Cinek4ffd6362015-12-29 15:12:23 +010042
Jorim Jaggi4e857f42014-11-17 19:14:04 +010043/**
44 * Wraps the actual notification content view; used to implement behaviors which are different for
45 * the individual templates and custom views.
46 */
Selim Cinek4ffd6362015-12-29 15:12:23 +010047public abstract class NotificationViewWrapper implements TransformableView {
Jorim Jaggi4e857f42014-11-17 19:14:04 +010048
49 protected final View mView;
Selim Cinek7d1c63e2016-04-21 15:26:10 -070050 protected final ExpandableNotificationRow mRow;
Adrian Roos7bcf6d32017-04-04 16:44:25 -070051
Lucas Dupine1bb9982019-01-24 16:42:52 -080052 protected int mBackgroundColor = 0;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010053
Selim Cinek7d1c63e2016-04-21 15:26:10 -070054 public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
Jorim Jaggibe4116a2015-05-20 20:04:08 -070055 if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
Selim Cinek0ffbda62016-01-01 20:29:12 +010056 if ("bigPicture".equals(v.getTag())) {
Selim Cinek7d1c63e2016-04-21 15:26:10 -070057 return new NotificationBigPictureTemplateViewWrapper(ctx, v, row);
Selim Cinekd634d062016-02-02 15:47:14 -080058 } else if ("bigText".equals(v.getTag())) {
Selim Cinek7d1c63e2016-04-21 15:26:10 -070059 return new NotificationBigTextTemplateViewWrapper(ctx, v, row);
Selim Cinekdf5bf612016-02-26 09:56:31 -080060 } else if ("media".equals(v.getTag()) || "bigMediaNarrow".equals(v.getTag())) {
Selim Cinek7d1c63e2016-04-21 15:26:10 -070061 return new NotificationMediaTemplateViewWrapper(ctx, v, row);
Adrian Roosfeafa052016-06-01 17:09:45 -070062 } else if ("messaging".equals(v.getTag())) {
63 return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
Selim Cinek0ffbda62016-01-01 20:29:12 +010064 }
Selim Cinek6fe4a102019-01-25 14:38:24 -080065 Class<? extends Notification.Style> style =
66 row.getEntry().notification.getNotification().getNotificationStyle();
67 if (Notification.DecoratedCustomViewStyle.class.equals(style)) {
68 return new NotificationDecoratedCustomViewWrapper(ctx, v, row);
69 }
Selim Cinek7d1c63e2016-04-21 15:26:10 -070070 return new NotificationTemplateViewWrapper(ctx, v, row);
Selim Cinek9c7712d2015-12-08 19:19:48 -080071 } else if (v instanceof NotificationHeaderView) {
Selim Cinek7d1c63e2016-04-21 15:26:10 -070072 return new NotificationHeaderViewWrapper(ctx, v, row);
Jorim Jaggi4e857f42014-11-17 19:14:04 +010073 } else {
Adrian Roos7bcf6d32017-04-04 16:44:25 -070074 return new NotificationCustomViewWrapper(ctx, v, row);
Jorim Jaggi4e857f42014-11-17 19:14:04 +010075 }
76 }
77
Adrian Roos7bcf6d32017-04-04 16:44:25 -070078 protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
Jorim Jaggi4e857f42014-11-17 19:14:04 +010079 mView = view;
Selim Cinek7d1c63e2016-04-21 15:26:10 -070080 mRow = row;
Selim Cinek131f1a42017-06-05 17:50:19 -070081 onReinflated();
Adrian Roos7bcf6d32017-04-04 16:44:25 -070082 }
83
Jorim Jaggidacc9242014-12-08 19:21:26 +010084 /**
85 * Notifies this wrapper that the content of the view might have changed.
Selim Cinek414ad332017-02-24 19:06:12 -080086 * @param row the row this wrapper is attached to
Jorim Jaggidacc9242014-12-08 19:21:26 +010087 */
Selim Cinek131f1a42017-06-05 17:50:19 -070088 public void onContentUpdated(ExpandableNotificationRow row) {
Selim Cinek131f1a42017-06-05 17:50:19 -070089 }
90
91 public void onReinflated() {
Selim Cinek245090f2017-02-02 10:36:02 -080092 if (shouldClearBackgroundOnReapply()) {
93 mBackgroundColor = 0;
94 }
Selim Cinek6fe4a102019-01-25 14:38:24 -080095 int backgroundColor = getBackgroundColor(mView);
96 if (backgroundColor != Color.TRANSPARENT) {
97 mBackgroundColor = backgroundColor;
98 mView.setBackground(new ColorDrawable(Color.TRANSPARENT));
99 }
100 }
101
102 protected boolean needsInversion(int defaultBackgroundColor, View view) {
103 if (view == null) {
104 return false;
105 }
106
107 Configuration configuration = mView.getResources().getConfiguration();
108 boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
109 == Configuration.UI_MODE_NIGHT_YES;
110 if (!nightMode) {
111 return false;
112 }
113
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -0800114 // Apps targeting Q should fix their dark mode bugs.
115 if (mRow.getEntry().targetSdk >= Build.VERSION_CODES.Q) {
116 return false;
117 }
118
Selim Cinek6fe4a102019-01-25 14:38:24 -0800119 int background = getBackgroundColor(view);
120 if (background == Color.TRANSPARENT) {
121 background = defaultBackgroundColor;
122 }
123 if (background == Color.TRANSPARENT) {
124 background = resolveBackgroundColor();
125 }
126
127 float[] hsl = new float[] {0f, 0f, 0f};
128 ColorUtils.colorToHSL(background, hsl);
129
130 // Notifications with colored backgrounds should not be inverted
131 if (hsl[1] != 0) {
132 return false;
133 }
134
135 // Invert white or light gray backgrounds.
136 boolean isLightGrayOrWhite = hsl[1] == 0 && hsl[2] > 0.5;
137 if (isLightGrayOrWhite) {
138 return true;
139 }
140
141 // Now let's check if there's unprotected text somewhere, and invert if we find it.
142 if (view instanceof ViewGroup) {
143 return childrenNeedInversion(background, (ViewGroup) view);
144 } else {
145 return false;
146 }
147 }
148
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -0800149 @VisibleForTesting
150 boolean childrenNeedInversion(@ColorInt int parentBackground, ViewGroup viewGroup) {
Selim Cinek6fe4a102019-01-25 14:38:24 -0800151 if (viewGroup == null) {
152 return false;
153 }
154
Lucas Dupin6cfa5cd2019-02-14 17:33:19 -0800155 int backgroundColor = getBackgroundColor(viewGroup);
156 if (Color.alpha(backgroundColor) != 255) {
157 backgroundColor = ContrastColorUtil.compositeColors(backgroundColor, parentBackground);
158 backgroundColor = ColorUtils.setAlphaComponent(backgroundColor, 255);
159 }
Selim Cinek6fe4a102019-01-25 14:38:24 -0800160 for (int i = 0; i < viewGroup.getChildCount(); i++) {
161 View child = viewGroup.getChildAt(i);
Selim Cinek6fe4a102019-01-25 14:38:24 -0800162 if (child instanceof TextView) {
163 int foreground = ((TextView) child).getCurrentTextColor();
164 if (ColorUtils.calculateContrast(foreground, backgroundColor) < 3) {
165 return true;
166 }
167 } else if (child instanceof ViewGroup) {
168 if (childrenNeedInversion(backgroundColor, (ViewGroup) child)) {
169 return true;
170 }
Gus Prevas9cc96602018-10-11 11:24:23 -0400171 }
Selim Cinek7b9605b2017-01-19 17:36:00 -0800172 }
Selim Cinek6fe4a102019-01-25 14:38:24 -0800173
174 return false;
175 }
176
177 protected int getBackgroundColor(View view) {
178 if (view == null) {
179 return Color.TRANSPARENT;
180 }
181 Drawable background = view.getBackground();
182 if (background instanceof ColorDrawable) {
183 return ((ColorDrawable) background).getColor();
184 }
185 return Color.TRANSPARENT;
186 }
187
188 protected void invertViewLuminosity(View view) {
189 Paint paint = new Paint();
190 ColorMatrix matrix = new ColorMatrix();
191 ColorMatrix tmp = new ColorMatrix();
192 // Inversion should happen on Y'UV space to conserve the colors and
193 // only affect the luminosity.
194 matrix.setRGB2YUV();
195 tmp.set(new float[]{
196 -1f, 0f, 0f, 0f, 255f,
197 0f, 1f, 0f, 0f, 0f,
198 0f, 0f, 1f, 0f, 0f,
199 0f, 0f, 0f, 1f, 0f
200 });
201 matrix.postConcat(tmp);
202 tmp.setYUV2RGB();
203 matrix.postConcat(tmp);
204 paint.setColorFilter(new ColorMatrixColorFilter(matrix));
205 view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
Selim Cinek7b9605b2017-01-19 17:36:00 -0800206 }
207
Selim Cinek245090f2017-02-02 10:36:02 -0800208 protected boolean shouldClearBackgroundOnReapply() {
209 return true;
210 }
211
Jorim Jaggibe4116a2015-05-20 20:04:08 -0700212 /**
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700213 * Update the appearance of the expand button.
214 *
215 * @param expandable should this view be expandable
216 * @param onClickListener the listener to invoke when the expand affordance is clicked on
217 */
218 public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
Selim Cinekea4bef72015-12-02 15:51:10 -0800219
220 /**
221 * @return the notification header if it exists
222 */
223 public NotificationHeaderView getNotificationHeader() {
224 return null;
225 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100226
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800227 public int getHeaderTranslation() {
228 return 0;
229 }
230
Selim Cinek4ffd6362015-12-29 15:12:23 +0100231 @Override
232 public TransformState getCurrentState(int fadingView) {
233 return null;
234 }
235
236 @Override
237 public void transformTo(TransformableView notification, Runnable endRunnable) {
238 // By default we are fading out completely
239 CrossFadeHelper.fadeOut(mView, endRunnable);
240 }
241
242 @Override
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800243 public void transformTo(TransformableView notification, float transformationAmount) {
244 CrossFadeHelper.fadeOut(mView, transformationAmount);
245 }
246
247 @Override
Selim Cinek4ffd6362015-12-29 15:12:23 +0100248 public void transformFrom(TransformableView notification) {
249 // By default we are fading in completely
250 CrossFadeHelper.fadeIn(mView);
251 }
252
253 @Override
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800254 public void transformFrom(TransformableView notification, float transformationAmount) {
255 CrossFadeHelper.fadeIn(mView, transformationAmount);
256 }
257
258 @Override
Selim Cinek4ffd6362015-12-29 15:12:23 +0100259 public void setVisible(boolean visible) {
Selim Cinekf64044c2016-02-11 18:18:08 -0800260 mView.animate().cancel();
Selim Cinek4ffd6362015-12-29 15:12:23 +0100261 mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
262 }
Selim Cinekc3179332016-03-04 14:44:56 -0800263
264 public int getCustomBackgroundColor() {
Selim Cinek7b9605b2017-01-19 17:36:00 -0800265 // Parent notifications should always use the normal background color
266 return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
Selim Cinekc3179332016-03-04 14:44:56 -0800267 }
268
Selim Cinek019d71e2018-04-19 10:24:39 +0800269 protected int resolveBackgroundColor() {
270 int customBackgroundColor = getCustomBackgroundColor();
271 if (customBackgroundColor != 0) {
272 return customBackgroundColor;
273 }
274 return mView.getContext().getColor(
275 com.android.internal.R.color.notification_material_background_color);
276 }
277
Selim Cinek1a48bab2017-02-17 19:38:40 -0800278 public void setLegacy(boolean legacy) {
Selim Cinekc3179332016-03-04 14:44:56 -0800279 }
Adrian Roos181385c2016-05-05 17:45:44 -0400280
281 public void setContentHeight(int contentHeight, int minHeightHint) {
282 }
Adrian Roos7b9ed0d2017-01-24 15:55:18 -0800283
284 public void setRemoteInputVisible(boolean visible) {
285 }
Selim Cinek414ad332017-02-24 19:06:12 -0800286
287 public void setIsChildInGroup(boolean isChildInGroup) {
288 }
Selim Cinek4705f292017-04-24 22:18:48 -0700289
290 public boolean isDimmable() {
291 return true;
292 }
Selim Cinek5d6ef8d2017-05-18 22:16:00 -0700293
294 public boolean disallowSingleClick(float x, float y) {
295 return false;
296 }
Selim Cineke62255c2017-09-28 18:23:23 -0700297
298 public int getMinLayoutHeight() {
299 return 0;
300 }
Selim Cinek515b2032017-11-15 10:20:19 -0800301
Selim Cinek86bfcee2018-01-17 11:00:47 -0800302 public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
Selim Cinek515b2032017-11-15 10:20:19 -0800303 return false;
304 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800305
306 public void setHeaderVisibleAmount(float headerVisibleAmount) {
307 }
Selim Cinek396caca2018-04-10 17:46:46 -0700308
309 /**
310 * Get the extra height that needs to be added to this view, such that it can be measured
311 * normally.
312 */
313 public int getExtraMeasureHeight() {
314 return 0;
315 }
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100316}