blob: 97d84433dafe990fd2bbb818fcf918098f201ffa [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 Cinek4b259a12019-05-09 19:10:51 -070019import static com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.DEFAULT_HEADER_VISIBLE_AMOUNT;
20
Selim Cinekd83203c2018-04-18 14:34:27 +080021import android.app.PendingIntent;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010022import android.content.Context;
Selim Cinek019d71e2018-04-19 10:24:39 +080023import android.content.res.ColorStateList;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010024import android.graphics.Color;
Selim Cinek019d71e2018-04-19 10:24:39 +080025import android.graphics.PorterDuffColorFilter;
Selim Cinek5d6ef8d2017-05-18 22:16:00 -070026import android.graphics.Rect;
Selim Cinek019d71e2018-04-19 10:24:39 +080027import android.graphics.drawable.Drawable;
Selim Cinek4ffd6362015-12-29 15:12:23 +010028import android.service.notification.StatusBarNotification;
Selim Cinekd83203c2018-04-18 14:34:27 +080029import android.util.ArraySet;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010030import android.view.View;
Selim Cinek019d71e2018-04-19 10:24:39 +080031import android.widget.Button;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010032import android.widget.ImageView;
Selim Cinek75fe38c2015-11-20 12:47:59 -080033import android.widget.ProgressBar;
Selim Cinek4ffd6362015-12-29 15:12:23 +010034import android.widget.TextView;
35
Lucas Dupina291d192018-06-07 13:59:42 -070036import com.android.internal.util.ContrastColorUtil;
Selim Cinek396caca2018-04-10 17:46:46 -070037import com.android.internal.widget.NotificationActionListLayout;
Selim Cinekd83203c2018-04-18 14:34:27 +080038import com.android.systemui.Dependency;
Selim Cinekbee4e072018-05-21 22:06:43 -070039import com.android.systemui.R;
Selim Cinekd83203c2018-04-18 14:34:27 +080040import com.android.systemui.UiOffloadThread;
Selim Cinekfd3e2622016-01-12 16:02:42 -080041import com.android.systemui.statusbar.CrossFadeHelper;
Gus Prevasab336792018-11-14 13:52:20 -050042import com.android.systemui.statusbar.TransformableView;
43import com.android.systemui.statusbar.ViewTransformationHelper;
Rohan Shah20790b82018-07-02 17:21:04 -070044import com.android.systemui.statusbar.notification.ImageTransformState;
45import com.android.systemui.statusbar.notification.TransformState;
46import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Rohan Shah20790b82018-07-02 17:21:04 -070047import com.android.systemui.statusbar.notification.row.HybridNotificationView;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070048
Jorim Jaggi4e857f42014-11-17 19:14:04 +010049/**
50 * Wraps a notification view inflated from a template.
51 */
Selim Cinek9c7712d2015-12-08 19:19:48 -080052public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
Jorim Jaggi4e857f42014-11-17 19:14:04 +010053
Selim Cinekc7e4cb52019-06-20 15:41:45 -070054 private final int mFullHeaderTranslation;
Jorim Jaggidacc9242014-12-08 19:21:26 +010055 protected ImageView mPicture;
Selim Cinek75fe38c2015-11-20 12:47:59 -080056 private ProgressBar mProgressBar;
Selim Cinek4ffd6362015-12-29 15:12:23 +010057 private TextView mTitle;
58 private TextView mText;
Selim Cineke62255c2017-09-28 18:23:23 -070059 protected View mActionsContainer;
Selim Cinek019d71e2018-04-19 10:24:39 +080060 private ImageView mReplyAction;
Selim Cinek5d6ef8d2017-05-18 22:16:00 -070061 private Rect mTmpRect = new Rect();
Adrian Roos181385c2016-05-05 17:45:44 -040062
63 private int mContentHeight;
64 private int mMinHeightHint;
Selim Cinek396caca2018-04-10 17:46:46 -070065 private NotificationActionListLayout mActions;
Selim Cinekd83203c2018-04-18 14:34:27 +080066 private ArraySet<PendingIntent> mCancelledPendingIntents = new ArraySet<>();
67 private UiOffloadThread mUiOffloadThread;
Selim Cinekbee4e072018-05-21 22:06:43 -070068 private View mRemoteInputHistory;
Selim Cinek4b259a12019-05-09 19:10:51 -070069 private float mHeaderTranslation;
Jorim Jaggi92df1f22014-12-16 19:44:41 +010070
Adrian Roos7bcf6d32017-04-04 16:44:25 -070071 protected NotificationTemplateViewWrapper(Context ctx, View view,
72 ExpandableNotificationRow row) {
Selim Cinek7d1c63e2016-04-21 15:26:10 -070073 super(ctx, view, row);
Selim Cinekfd3e2622016-01-12 16:02:42 -080074 mTransformationHelper.setCustomTransformation(
75 new ViewTransformationHelper.CustomTransformation() {
76 @Override
77 public boolean transformTo(TransformState ownState,
Selim Cinek8f2f6a62016-02-23 19:56:31 -080078 TransformableView notification, final float transformationAmount) {
Selim Cinekfd3e2622016-01-12 16:02:42 -080079 if (!(notification instanceof HybridNotificationView)) {
80 return false;
81 }
82 TransformState otherState = notification.getCurrentState(
83 TRANSFORMING_VIEW_TITLE);
Selim Cinekb66685b2016-02-02 17:16:08 -080084 final View text = ownState.getTransformedView();
Selim Cinek8f2f6a62016-02-23 19:56:31 -080085 CrossFadeHelper.fadeOut(text, transformationAmount);
Selim Cinekfd3e2622016-01-12 16:02:42 -080086 if (otherState != null) {
Selim Cinek8f2f6a62016-02-23 19:56:31 -080087 ownState.transformViewVerticalTo(otherState, this,
88 transformationAmount);
Selim Cinekfd3e2622016-01-12 16:02:42 -080089 otherState.recycle();
90 }
91 return true;
92 }
93
94 @Override
Selim Cinek8f2f6a62016-02-23 19:56:31 -080095 public boolean customTransformTarget(TransformState ownState,
96 TransformState otherState) {
97 float endY = getTransformationY(ownState, otherState);
98 ownState.setTransformationEndY(endY);
99 return true;
100 }
101
102 @Override
Selim Cinekfd3e2622016-01-12 16:02:42 -0800103 public boolean transformFrom(TransformState ownState,
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800104 TransformableView notification, float transformationAmount) {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800105 if (!(notification instanceof HybridNotificationView)) {
106 return false;
107 }
108 TransformState otherState = notification.getCurrentState(
109 TRANSFORMING_VIEW_TITLE);
Selim Cinekb66685b2016-02-02 17:16:08 -0800110 final View text = ownState.getTransformedView();
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800111 CrossFadeHelper.fadeIn(text, transformationAmount);
Selim Cinekfd3e2622016-01-12 16:02:42 -0800112 if (otherState != null) {
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800113 ownState.transformViewVerticalFrom(otherState, this,
114 transformationAmount);
Selim Cinekfd3e2622016-01-12 16:02:42 -0800115 otherState.recycle();
116 }
117 return true;
118 }
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800119
120 @Override
121 public boolean initTransformation(TransformState ownState,
122 TransformState otherState) {
123 float startY = getTransformationY(ownState, otherState);
124 ownState.setTransformationStartY(startY);
125 return true;
126 }
127
128 private float getTransformationY(TransformState ownState,
129 TransformState otherState) {
130 int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
131 int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
132 return (otherStablePosition[1]
133 + otherState.getTransformedView().getHeight()
134 - ownStablePosition[1]) * 0.33f;
135 }
136
Selim Cinekfd3e2622016-01-12 16:02:42 -0800137 }, TRANSFORMING_VIEW_TEXT);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700138 mFullHeaderTranslation = ctx.getResources().getDimensionPixelSize(
Selim Cinek4b259a12019-05-09 19:10:51 -0700139 com.android.internal.R.dimen.notification_content_margin)
140 - ctx.getResources().getDimensionPixelSize(
141 com.android.internal.R.dimen.notification_content_margin_top);
Jorim Jaggidacc9242014-12-08 19:21:26 +0100142 }
143
Selim Cinek4ffd6362015-12-29 15:12:23 +0100144 private void resolveTemplateViews(StatusBarNotification notification) {
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700145 mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
Adrian Roos0aac04f2016-12-08 15:59:29 -0800146 if (mPicture != null) {
147 mPicture.setTag(ImageTransformState.ICON_TAG,
148 notification.getNotification().getLargeIcon());
149 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100150 mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
151 mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
Selim Cinek75fe38c2015-11-20 12:47:59 -0800152 final View progress = mView.findViewById(com.android.internal.R.id.progress);
153 if (progress instanceof ProgressBar) {
154 mProgressBar = (ProgressBar) progress;
155 } else {
156 // It's still a viewstub
157 mProgressBar = null;
158 }
Adrian Roos181385c2016-05-05 17:45:44 -0400159 mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
Selim Cinek396caca2018-04-10 17:46:46 -0700160 mActions = mView.findViewById(com.android.internal.R.id.actions);
Selim Cinek5d6ef8d2017-05-18 22:16:00 -0700161 mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action);
Selim Cinekbee4e072018-05-21 22:06:43 -0700162 mRemoteInputHistory = mView.findViewById(
163 com.android.internal.R.id.notification_material_reply_container);
Selim Cinekd83203c2018-04-18 14:34:27 +0800164 updatePendingIntentCancellations();
165 }
166
167 private void updatePendingIntentCancellations() {
168 if (mActions != null) {
169 int numActions = mActions.getChildCount();
170 for (int i = 0; i < numActions; i++) {
Selim Cinek019d71e2018-04-19 10:24:39 +0800171 Button action = (Button) mActions.getChildAt(i);
Selim Cinekd83203c2018-04-18 14:34:27 +0800172 performOnPendingIntentCancellation(action, () -> {
Selim Cinek019d71e2018-04-19 10:24:39 +0800173 if (action.isEnabled()) {
174 action.setEnabled(false);
175 // The visual appearance doesn't look disabled enough yet, let's add the
176 // alpha as well. Since Alpha doesn't play nicely right now with the
177 // transformation, we rather blend it manually with the background color.
178 ColorStateList textColors = action.getTextColors();
179 int[] colors = textColors.getColors();
180 int[] newColors = new int[colors.length];
181 float disabledAlpha = mView.getResources().getFloat(
182 com.android.internal.R.dimen.notification_action_disabled_alpha);
183 for (int j = 0; j < colors.length; j++) {
184 int color = colors[j];
185 color = blendColorWithBackground(color, disabledAlpha);
186 newColors[j] = color;
187 }
188 ColorStateList newColorStateList = new ColorStateList(
189 textColors.getStates(), newColors);
190 action.setTextColor(newColorStateList);
191 }
Selim Cinekd83203c2018-04-18 14:34:27 +0800192 });
193 }
194 }
195 if (mReplyAction != null) {
Selim Cinek27732cc2018-05-29 17:01:05 -0700196 // Let's reset the view on update, assuming the new pending intent isn't cancelled
197 // anymore. The color filter automatically resets when it's updated.
198 mReplyAction.setEnabled(true);
Selim Cinekd83203c2018-04-18 14:34:27 +0800199 performOnPendingIntentCancellation(mReplyAction, () -> {
Selim Cinek019d71e2018-04-19 10:24:39 +0800200 if (mReplyAction != null && mReplyAction.isEnabled()) {
201 mReplyAction.setEnabled(false);
202 // The visual appearance doesn't look disabled enough yet, let's add the
203 // alpha as well. Since Alpha doesn't play nicely right now with the
204 // transformation, we rather blend it manually with the background color.
205 Drawable drawable = mReplyAction.getDrawable().mutate();
206 PorterDuffColorFilter colorFilter =
207 (PorterDuffColorFilter) drawable.getColorFilter();
208 float disabledAlpha = mView.getResources().getFloat(
209 com.android.internal.R.dimen.notification_action_disabled_alpha);
210 if (colorFilter != null) {
211 int color = colorFilter.getColor();
212 color = blendColorWithBackground(color, disabledAlpha);
213 drawable.mutate().setColorFilter(color, colorFilter.getMode());
214 } else {
215 mReplyAction.setAlpha(disabledAlpha);
216 }
217 }
Selim Cinekd83203c2018-04-18 14:34:27 +0800218 });
219 }
220 }
221
Selim Cinek019d71e2018-04-19 10:24:39 +0800222 private int blendColorWithBackground(int color, float alpha) {
223 // alpha doesn't go well for color filters, so let's blend it manually
Lucas Dupina291d192018-06-07 13:59:42 -0700224 return ContrastColorUtil.compositeColors(Color.argb((int) (alpha * 255),
Selim Cinek019d71e2018-04-19 10:24:39 +0800225 Color.red(color), Color.green(color), Color.blue(color)), resolveBackgroundColor());
226 }
227
Selim Cinekd83203c2018-04-18 14:34:27 +0800228 private void performOnPendingIntentCancellation(View view, Runnable cancellationRunnable) {
229 PendingIntent pendingIntent = (PendingIntent) view.getTag(
230 com.android.internal.R.id.pending_intent_tag);
231 if (pendingIntent == null) {
232 return;
233 }
234 if (mCancelledPendingIntents.contains(pendingIntent)) {
235 cancellationRunnable.run();
236 } else {
237 PendingIntent.CancelListener listener = (PendingIntent intent) -> {
238 mView.post(() -> {
239 mCancelledPendingIntents.add(pendingIntent);
240 cancellationRunnable.run();
241 });
242 };
243 if (mUiOffloadThread == null) {
244 mUiOffloadThread = Dependency.get(UiOffloadThread.class);
245 }
Selim Cinek75f30cd2018-05-04 13:13:09 -0700246 if (view.isAttachedToWindow()) {
247 mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
248 }
Selim Cinekd83203c2018-04-18 14:34:27 +0800249 view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
250 @Override
251 public void onViewAttachedToWindow(View v) {
252 mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
253 }
254
255 @Override
256 public void onViewDetachedFromWindow(View v) {
257 mUiOffloadThread.submit(() -> pendingIntent.unregisterCancelListener(listener));
258 }
259 });
260 }
Selim Cinek5d6ef8d2017-05-18 22:16:00 -0700261 }
262
263 @Override
264 public boolean disallowSingleClick(float x, float y) {
265 if (mReplyAction != null && mReplyAction.getVisibility() == View.VISIBLE) {
266 if (isOnView(mReplyAction, x, y) || isOnView(mPicture, x, y)) {
267 return true;
268 }
269 }
270 return super.disallowSingleClick(x, y);
271 }
272
273 private boolean isOnView(View view, float x, float y) {
274 View searchView = (View) view.getParent();
275 while (searchView != null && !(searchView instanceof ExpandableNotificationRow)) {
276 searchView.getHitRect(mTmpRect);
277 x -= mTmpRect.left;
278 y -= mTmpRect.top;
279 searchView = (View) searchView.getParent();
280 }
281 view.getHitRect(mTmpRect);
282 return mTmpRect.contains((int) x,(int) y);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100283 }
284
285 @Override
Selim Cinek131f1a42017-06-05 17:50:19 -0700286 public void onContentUpdated(ExpandableNotificationRow row) {
Selim Cinek646d2052016-01-03 14:42:02 +0800287 // Reinspect the notification. Before the super call, because the super call also updates
288 // the transformation types and we need to have our values set by then.
Selim Cineka6ee4bd2017-09-20 22:52:29 +0000289 resolveTemplateViews(row.getStatusBarNotification());
Selim Cinek131f1a42017-06-05 17:50:19 -0700290 super.onContentUpdated(row);
Selim Cinek4b259a12019-05-09 19:10:51 -0700291 if (row.getHeaderVisibleAmount() != DEFAULT_HEADER_VISIBLE_AMOUNT) {
292 setHeaderVisibleAmount(row.getHeaderVisibleAmount());
293 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100294 }
295
296 @Override
Selim Cinek4ffd6362015-12-29 15:12:23 +0100297 protected void updateTransformedTypes() {
298 // This also clears the existing types
299 super.updateTransformedTypes();
300 if (mTitle != null) {
Adrian Roos7bcf6d32017-04-04 16:44:25 -0700301 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
302 mTitle);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100303 }
304 if (mText != null) {
Adrian Roos7bcf6d32017-04-04 16:44:25 -0700305 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
306 mText);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100307 }
308 if (mPicture != null) {
Adrian Roos7bcf6d32017-04-04 16:44:25 -0700309 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
310 mPicture);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100311 }
312 if (mProgressBar != null) {
Adrian Roos7bcf6d32017-04-04 16:44:25 -0700313 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
314 mProgressBar);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100315 }
Jorim Jaggidacc9242014-12-08 19:21:26 +0100316 }
317
318 @Override
Adrian Roos181385c2016-05-05 17:45:44 -0400319 public void setContentHeight(int contentHeight, int minHeightHint) {
320 super.setContentHeight(contentHeight, minHeightHint);
321
322 mContentHeight = contentHeight;
323 mMinHeightHint = minHeightHint;
324 updateActionOffset();
325 }
326
Selim Cinek86bfcee2018-01-17 11:00:47 -0800327 @Override
328 public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
329 if (super.shouldClipToRounding(topRounded, bottomRounded)) {
330 return true;
331 }
332 return bottomRounded && mActionsContainer != null
333 && mActionsContainer.getVisibility() != View.GONE;
334 }
335
Adrian Roos181385c2016-05-05 17:45:44 -0400336 private void updateActionOffset() {
337 if (mActionsContainer != null) {
338 // We should never push the actions higher than they are in the headsup view.
339 int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800340
341 // We also need to compensate for any header translation, since we're always at the end.
342 mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight()
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700343 - getHeaderTranslation(false /* forceNoHeader */));
Adrian Roos181385c2016-05-05 17:45:44 -0400344 }
345 }
Selim Cinek396caca2018-04-10 17:46:46 -0700346
347 @Override
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700348 public int getHeaderTranslation(boolean forceNoHeader) {
349 return forceNoHeader ? mFullHeaderTranslation : (int) mHeaderTranslation;
Selim Cinek4b259a12019-05-09 19:10:51 -0700350 }
351
352 @Override
353 public void setHeaderVisibleAmount(float headerVisibleAmount) {
354 super.setHeaderVisibleAmount(headerVisibleAmount);
355 mNotificationHeader.setAlpha(headerVisibleAmount);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700356 mHeaderTranslation = (1.0f - headerVisibleAmount) * mFullHeaderTranslation;
Selim Cinek4b259a12019-05-09 19:10:51 -0700357 mView.setTranslationY(mHeaderTranslation);
358 }
359
360 @Override
Selim Cinek396caca2018-04-10 17:46:46 -0700361 public int getExtraMeasureHeight() {
362 int extra = 0;
363 if (mActions != null) {
364 extra = mActions.getExtraMeasureHeight();
365 }
Selim Cinekbee4e072018-05-21 22:06:43 -0700366 if (mRemoteInputHistory != null && mRemoteInputHistory.getVisibility() != View.GONE) {
367 extra += mRow.getContext().getResources().getDimensionPixelSize(
368 R.dimen.remote_input_history_extra_height);
369 }
Selim Cinek396caca2018-04-10 17:46:46 -0700370 return extra + super.getExtraMeasureHeight();
371 }
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100372}