Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 1 | /* |
| 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 Shah | 20790b8 | 2018-07-02 17:21:04 -0700 | [diff] [blame] | 17 | package com.android.systemui.statusbar.notification.row.wrapper; |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 18 | |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 19 | import android.annotation.ColorInt; |
| 20 | import android.app.Notification; |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 21 | import android.content.Context; |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 22 | import android.content.res.Configuration; |
Gus Prevas | 9cc9660 | 2018-10-11 11:24:23 -0400 | [diff] [blame] | 23 | import android.graphics.Color; |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 24 | import android.graphics.ColorMatrix; |
| 25 | import android.graphics.ColorMatrixColorFilter; |
| 26 | import android.graphics.Paint; |
Selim Cinek | 7b9605b | 2017-01-19 17:36:00 -0800 | [diff] [blame] | 27 | import android.graphics.drawable.ColorDrawable; |
| 28 | import android.graphics.drawable.Drawable; |
Lucas Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 29 | import android.os.Build; |
Selim Cinek | ea4bef7 | 2015-12-02 15:51:10 -0800 | [diff] [blame] | 30 | import android.view.NotificationHeaderView; |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 31 | import android.view.View; |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 32 | import android.view.ViewGroup; |
| 33 | import android.widget.TextView; |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 34 | |
Lucas Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 35 | import com.android.internal.annotations.VisibleForTesting; |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 36 | import com.android.internal.graphics.ColorUtils; |
Lucas Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 37 | import com.android.internal.util.ContrastColorUtil; |
Selim Cinek | 0ffbda6 | 2016-01-01 20:29:12 +0100 | [diff] [blame] | 38 | import com.android.systemui.statusbar.CrossFadeHelper; |
Gus Prevas | ab33679 | 2018-11-14 13:52:20 -0500 | [diff] [blame] | 39 | import com.android.systemui.statusbar.TransformableView; |
Rohan Shah | 20790b8 | 2018-07-02 17:21:04 -0700 | [diff] [blame] | 40 | import com.android.systemui.statusbar.notification.TransformState; |
| 41 | import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 42 | |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 43 | /** |
| 44 | * Wraps the actual notification content view; used to implement behaviors which are different for |
| 45 | * the individual templates and custom views. |
| 46 | */ |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 47 | public abstract class NotificationViewWrapper implements TransformableView { |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 48 | |
| 49 | protected final View mView; |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 50 | protected final ExpandableNotificationRow mRow; |
Adrian Roos | 7bcf6d3 | 2017-04-04 16:44:25 -0700 | [diff] [blame] | 51 | |
Lucas Dupin | e1bb998 | 2019-01-24 16:42:52 -0800 | [diff] [blame] | 52 | protected int mBackgroundColor = 0; |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 53 | |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 54 | public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { |
Jorim Jaggi | be4116a | 2015-05-20 20:04:08 -0700 | [diff] [blame] | 55 | if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { |
Selim Cinek | 0ffbda6 | 2016-01-01 20:29:12 +0100 | [diff] [blame] | 56 | if ("bigPicture".equals(v.getTag())) { |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 57 | return new NotificationBigPictureTemplateViewWrapper(ctx, v, row); |
Selim Cinek | d634d06 | 2016-02-02 15:47:14 -0800 | [diff] [blame] | 58 | } else if ("bigText".equals(v.getTag())) { |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 59 | return new NotificationBigTextTemplateViewWrapper(ctx, v, row); |
Selim Cinek | df5bf61 | 2016-02-26 09:56:31 -0800 | [diff] [blame] | 60 | } else if ("media".equals(v.getTag()) || "bigMediaNarrow".equals(v.getTag())) { |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 61 | return new NotificationMediaTemplateViewWrapper(ctx, v, row); |
Adrian Roos | feafa05 | 2016-06-01 17:09:45 -0700 | [diff] [blame] | 62 | } else if ("messaging".equals(v.getTag())) { |
| 63 | return new NotificationMessagingTemplateViewWrapper(ctx, v, row); |
Selim Cinek | 0ffbda6 | 2016-01-01 20:29:12 +0100 | [diff] [blame] | 64 | } |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 65 | 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 Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 70 | return new NotificationTemplateViewWrapper(ctx, v, row); |
Selim Cinek | 9c7712d | 2015-12-08 19:19:48 -0800 | [diff] [blame] | 71 | } else if (v instanceof NotificationHeaderView) { |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 72 | return new NotificationHeaderViewWrapper(ctx, v, row); |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 73 | } else { |
Adrian Roos | 7bcf6d3 | 2017-04-04 16:44:25 -0700 | [diff] [blame] | 74 | return new NotificationCustomViewWrapper(ctx, v, row); |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 75 | } |
| 76 | } |
| 77 | |
Adrian Roos | 7bcf6d3 | 2017-04-04 16:44:25 -0700 | [diff] [blame] | 78 | protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { |
Jorim Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 79 | mView = view; |
Selim Cinek | 7d1c63e | 2016-04-21 15:26:10 -0700 | [diff] [blame] | 80 | mRow = row; |
Selim Cinek | 131f1a4 | 2017-06-05 17:50:19 -0700 | [diff] [blame] | 81 | onReinflated(); |
Adrian Roos | 7bcf6d3 | 2017-04-04 16:44:25 -0700 | [diff] [blame] | 82 | } |
| 83 | |
Jorim Jaggi | dacc924 | 2014-12-08 19:21:26 +0100 | [diff] [blame] | 84 | /** |
| 85 | * Notifies this wrapper that the content of the view might have changed. |
Selim Cinek | 414ad33 | 2017-02-24 19:06:12 -0800 | [diff] [blame] | 86 | * @param row the row this wrapper is attached to |
Jorim Jaggi | dacc924 | 2014-12-08 19:21:26 +0100 | [diff] [blame] | 87 | */ |
Selim Cinek | 131f1a4 | 2017-06-05 17:50:19 -0700 | [diff] [blame] | 88 | public void onContentUpdated(ExpandableNotificationRow row) { |
Selim Cinek | 131f1a4 | 2017-06-05 17:50:19 -0700 | [diff] [blame] | 89 | } |
| 90 | |
| 91 | public void onReinflated() { |
Selim Cinek | 245090f | 2017-02-02 10:36:02 -0800 | [diff] [blame] | 92 | if (shouldClearBackgroundOnReapply()) { |
| 93 | mBackgroundColor = 0; |
| 94 | } |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 95 | 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 Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 114 | // Apps targeting Q should fix their dark mode bugs. |
| 115 | if (mRow.getEntry().targetSdk >= Build.VERSION_CODES.Q) { |
| 116 | return false; |
| 117 | } |
| 118 | |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 119 | 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 Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 149 | @VisibleForTesting |
| 150 | boolean childrenNeedInversion(@ColorInt int parentBackground, ViewGroup viewGroup) { |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 151 | if (viewGroup == null) { |
| 152 | return false; |
| 153 | } |
| 154 | |
Lucas Dupin | 6cfa5cd | 2019-02-14 17:33:19 -0800 | [diff] [blame] | 155 | int backgroundColor = getBackgroundColor(viewGroup); |
| 156 | if (Color.alpha(backgroundColor) != 255) { |
| 157 | backgroundColor = ContrastColorUtil.compositeColors(backgroundColor, parentBackground); |
| 158 | backgroundColor = ColorUtils.setAlphaComponent(backgroundColor, 255); |
| 159 | } |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 160 | for (int i = 0; i < viewGroup.getChildCount(); i++) { |
| 161 | View child = viewGroup.getChildAt(i); |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 162 | 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 Prevas | 9cc9660 | 2018-10-11 11:24:23 -0400 | [diff] [blame] | 171 | } |
Selim Cinek | 7b9605b | 2017-01-19 17:36:00 -0800 | [diff] [blame] | 172 | } |
Selim Cinek | 6fe4a10 | 2019-01-25 14:38:24 -0800 | [diff] [blame] | 173 | |
| 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 Cinek | 7b9605b | 2017-01-19 17:36:00 -0800 | [diff] [blame] | 206 | } |
| 207 | |
Selim Cinek | 245090f | 2017-02-02 10:36:02 -0800 | [diff] [blame] | 208 | protected boolean shouldClearBackgroundOnReapply() { |
| 209 | return true; |
| 210 | } |
| 211 | |
Jorim Jaggi | be4116a | 2015-05-20 20:04:08 -0700 | [diff] [blame] | 212 | /** |
Selim Cinek | 65b2e7c | 2015-10-26 14:11:31 -0700 | [diff] [blame] | 213 | * 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 Cinek | ea4bef7 | 2015-12-02 15:51:10 -0800 | [diff] [blame] | 219 | |
| 220 | /** |
| 221 | * @return the notification header if it exists |
| 222 | */ |
| 223 | public NotificationHeaderView getNotificationHeader() { |
| 224 | return null; |
| 225 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 226 | |
Selim Cinek | aa9db1f | 2018-02-27 17:35:47 -0800 | [diff] [blame] | 227 | public int getHeaderTranslation() { |
| 228 | return 0; |
| 229 | } |
| 230 | |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 231 | @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 Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 243 | public void transformTo(TransformableView notification, float transformationAmount) { |
| 244 | CrossFadeHelper.fadeOut(mView, transformationAmount); |
| 245 | } |
| 246 | |
| 247 | @Override |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 248 | public void transformFrom(TransformableView notification) { |
| 249 | // By default we are fading in completely |
| 250 | CrossFadeHelper.fadeIn(mView); |
| 251 | } |
| 252 | |
| 253 | @Override |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 254 | public void transformFrom(TransformableView notification, float transformationAmount) { |
| 255 | CrossFadeHelper.fadeIn(mView, transformationAmount); |
| 256 | } |
| 257 | |
| 258 | @Override |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 259 | public void setVisible(boolean visible) { |
Selim Cinek | f64044c | 2016-02-11 18:18:08 -0800 | [diff] [blame] | 260 | mView.animate().cancel(); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 261 | mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); |
| 262 | } |
Selim Cinek | c317933 | 2016-03-04 14:44:56 -0800 | [diff] [blame] | 263 | |
| 264 | public int getCustomBackgroundColor() { |
Selim Cinek | 7b9605b | 2017-01-19 17:36:00 -0800 | [diff] [blame] | 265 | // Parent notifications should always use the normal background color |
| 266 | return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor; |
Selim Cinek | c317933 | 2016-03-04 14:44:56 -0800 | [diff] [blame] | 267 | } |
| 268 | |
Selim Cinek | 019d71e | 2018-04-19 10:24:39 +0800 | [diff] [blame] | 269 | 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 Cinek | 1a48bab | 2017-02-17 19:38:40 -0800 | [diff] [blame] | 278 | public void setLegacy(boolean legacy) { |
Selim Cinek | c317933 | 2016-03-04 14:44:56 -0800 | [diff] [blame] | 279 | } |
Adrian Roos | 181385c | 2016-05-05 17:45:44 -0400 | [diff] [blame] | 280 | |
| 281 | public void setContentHeight(int contentHeight, int minHeightHint) { |
| 282 | } |
Adrian Roos | 7b9ed0d | 2017-01-24 15:55:18 -0800 | [diff] [blame] | 283 | |
| 284 | public void setRemoteInputVisible(boolean visible) { |
| 285 | } |
Selim Cinek | 414ad33 | 2017-02-24 19:06:12 -0800 | [diff] [blame] | 286 | |
| 287 | public void setIsChildInGroup(boolean isChildInGroup) { |
| 288 | } |
Selim Cinek | 4705f29 | 2017-04-24 22:18:48 -0700 | [diff] [blame] | 289 | |
| 290 | public boolean isDimmable() { |
| 291 | return true; |
| 292 | } |
Selim Cinek | 5d6ef8d | 2017-05-18 22:16:00 -0700 | [diff] [blame] | 293 | |
| 294 | public boolean disallowSingleClick(float x, float y) { |
| 295 | return false; |
| 296 | } |
Selim Cinek | e62255c | 2017-09-28 18:23:23 -0700 | [diff] [blame] | 297 | |
| 298 | public int getMinLayoutHeight() { |
| 299 | return 0; |
| 300 | } |
Selim Cinek | 515b203 | 2017-11-15 10:20:19 -0800 | [diff] [blame] | 301 | |
Selim Cinek | 86bfcee | 2018-01-17 11:00:47 -0800 | [diff] [blame] | 302 | public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { |
Selim Cinek | 515b203 | 2017-11-15 10:20:19 -0800 | [diff] [blame] | 303 | return false; |
| 304 | } |
Selim Cinek | aa9db1f | 2018-02-27 17:35:47 -0800 | [diff] [blame] | 305 | |
| 306 | public void setHeaderVisibleAmount(float headerVisibleAmount) { |
| 307 | } |
Selim Cinek | 396caca | 2018-04-10 17:46:46 -0700 | [diff] [blame] | 308 | |
| 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 Jaggi | 4e857f4 | 2014-11-17 19:14:04 +0100 | [diff] [blame] | 316 | } |