blob: 20e089be374c6e174c457fa40c272a6be558b7ae [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
17package com.android.systemui.statusbar;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.Color;
24import android.graphics.ColorFilter;
25import android.graphics.ColorMatrix;
26import android.graphics.ColorMatrixColorFilter;
27import android.graphics.PorterDuff;
28import android.graphics.PorterDuffColorFilter;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070029import android.graphics.Rect;
Jorim Jaggidacc9242014-12-08 19:21:26 +010030import android.graphics.drawable.Drawable;
Selim Cinek8d6440d2015-10-22 13:00:05 -070031import android.text.TextUtils;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070032import android.view.MotionEvent;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010033import android.view.View;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070034import android.view.ViewConfiguration;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010035import android.view.animation.AnimationUtils;
36import android.view.animation.Interpolator;
37import android.widget.ImageView;
Selim Cinek8d6440d2015-10-22 13:00:05 -070038import android.widget.TextView;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010039
40import com.android.systemui.R;
41import com.android.systemui.ViewInvertHelper;
42import com.android.systemui.statusbar.phone.NotificationPanelView;
43
Selim Cinek65b2e7c2015-10-26 14:11:31 -070044import java.util.ArrayList;
45
Jorim Jaggi4e857f42014-11-17 19:14:04 +010046/**
47 * Wraps a notification view inflated from a template.
48 */
49public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
50
Jorim Jaggi4e857f42014-11-17 19:14:04 +010051 private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
52 private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
53 0, PorterDuff.Mode.SRC_ATOP);
54 private final int mIconDarkAlpha;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070055 private final int mIconDarkColor;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010056 private final Interpolator mLinearOutSlowInInterpolator;
57
Selim Cinek65b2e7c2015-10-26 14:11:31 -070058 private int mColor;
Jorim Jaggidacc9242014-12-08 19:21:26 +010059 private ViewInvertHelper mInvertHelper;
60 private ImageView mIcon;
61 protected ImageView mPicture;
62
Selim Cinek65b2e7c2015-10-26 14:11:31 -070063 /**
64 * Whether the icon needs to be forced grayscale when in dark mode.
65 */
Jorim Jaggi92df1f22014-12-16 19:44:41 +010066 private boolean mIconForceGraysaleWhenDark;
Selim Cinek8d6440d2015-10-22 13:00:05 -070067 private TextView mSubText;
68 private TextView mInfoText;
69 private View mProfileBadge;
Selim Cinek8d6440d2015-10-22 13:00:05 -070070 private View mThirdLine;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070071 private ImageView mExpandButton;
72 private View mNotificationHeader;
73 private View.OnClickListener mExpandClickListener;
74 private HeaderTouchListener mHeaderTouchListener;
Jorim Jaggi92df1f22014-12-16 19:44:41 +010075
Jorim Jaggi4e857f42014-11-17 19:14:04 +010076 protected NotificationTemplateViewWrapper(Context ctx, View view) {
77 super(view);
78 mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
Selim Cinek65b2e7c2015-10-26 14:11:31 -070079 mIconDarkColor =
Alan Viverette4a357cd2015-03-18 18:37:18 -070080 ctx.getColor(R.color.doze_small_icon_background_color);
Jorim Jaggi4e857f42014-11-17 19:14:04 +010081 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
82 android.R.interpolator.linear_out_slow_in);
Jorim Jaggidacc9242014-12-08 19:21:26 +010083 resolveViews();
84 }
85
86 private void resolveViews() {
87 View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
Jorim Jaggi4e857f42014-11-17 19:14:04 +010088 mInvertHelper = mainColumn != null
89 ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
90 : null;
Selim Cinek65b2e7c2015-10-26 14:11:31 -070091 mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
92 mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
Selim Cinek8d6440d2015-10-22 13:00:05 -070093 mSubText = (TextView) mView.findViewById(com.android.internal.R.id.text);
94 mInfoText = (TextView) mView.findViewById(com.android.internal.R.id.info);
95 mProfileBadge = mView.findViewById(com.android.internal.R.id.profile_badge_line3);
Selim Cinek8d6440d2015-10-22 13:00:05 -070096 mThirdLine = mView.findViewById(com.android.internal.R.id.line3);
Selim Cinek65b2e7c2015-10-26 14:11:31 -070097 mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
98 mColor = resolveColor(mExpandButton);
99 mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
100 // Post to make sure the parent lays out its children before we get their bounds
101 mHeaderTouchListener = new HeaderTouchListener();
102 mExpandButton.post(new Runnable() {
103 @Override
104 public void run() {
105 // let's set up our touch regions
106 mHeaderTouchListener.bindTouchRects(mNotificationHeader, mIcon, mExpandButton);
107 }
108 });
Jorim Jaggi92df1f22014-12-16 19:44:41 +0100109
110 // If the icon already has a color filter, we assume that we already forced the icon to be
111 // white when we created the notification.
Dan Sandler06433bd2015-02-25 13:20:39 -0500112 final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
113 mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100114 }
115
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700116 private int resolveColor(ImageView icon) {
117 if (icon != null && icon.getDrawable() != null) {
118 ColorFilter filter = icon.getDrawable().getColorFilter();
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100119 if (filter instanceof PorterDuffColorFilter) {
120 return ((PorterDuffColorFilter) filter).getColor();
121 }
122 }
123 return 0;
124 }
125
126 @Override
Jorim Jaggidacc9242014-12-08 19:21:26 +0100127 public void notifyContentUpdated() {
128 super.notifyContentUpdated();
129
130 // Reinspect the notification.
131 resolveViews();
132 }
133
134 @Override
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100135 public void setDark(boolean dark, boolean fade, long delay) {
Jorim Jaggi394a5d62014-11-26 23:07:13 +0100136 if (mInvertHelper != null) {
137 if (fade) {
138 mInvertHelper.fade(dark, delay);
139 } else {
140 mInvertHelper.update(dark);
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100141 }
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100142 }
Jorim Jaggi394a5d62014-11-26 23:07:13 +0100143 if (mIcon != null) {
144 if (fade) {
145 fadeIconColorFilter(mIcon, dark, delay);
146 fadeIconAlpha(mIcon, dark, delay);
Jorim Jaggi92df1f22014-12-16 19:44:41 +0100147 if (!mIconForceGraysaleWhenDark) {
148 fadeGrayscale(mIcon, dark, delay);
149 }
Jorim Jaggi394a5d62014-11-26 23:07:13 +0100150 } else {
151 updateIconColorFilter(mIcon, dark);
152 updateIconAlpha(mIcon, dark);
Jorim Jaggi92df1f22014-12-16 19:44:41 +0100153 if (!mIconForceGraysaleWhenDark) {
154 updateGrayscale(mIcon, dark);
155 }
Jorim Jaggi394a5d62014-11-26 23:07:13 +0100156 }
157 }
158 setPictureGrayscale(dark, fade, delay);
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100159 }
160
161 protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
162 if (mPicture != null) {
163 if (fade) {
164 fadeGrayscale(mPicture, grayscale, delay);
165 } else {
166 updateGrayscale(mPicture, grayscale);
167 }
168 }
169 }
170
171 private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
172 boolean dark, long delay, Animator.AnimatorListener listener) {
173 float startIntensity = dark ? 0f : 1f;
174 float endIntensity = dark ? 1f : 0f;
175 ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
176 animator.addUpdateListener(updateListener);
177 animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
178 animator.setInterpolator(mLinearOutSlowInInterpolator);
179 animator.setStartDelay(delay);
180 if (listener != null) {
181 animator.addListener(listener);
182 }
183 animator.start();
184 }
185
186 private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
187 startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
188 @Override
189 public void onAnimationUpdate(ValueAnimator animation) {
190 updateIconColorFilter(target, (Float) animation.getAnimatedValue());
191 }
192 }, dark, delay, null /* listener */);
193 }
194
195 private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
196 startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
197 @Override
198 public void onAnimationUpdate(ValueAnimator animation) {
199 float t = (float) animation.getAnimatedValue();
200 target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
201 }
202 }, dark, delay, null /* listener */);
203 }
204
205 protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
206 startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
207 @Override
208 public void onAnimationUpdate(ValueAnimator animation) {
209 updateGrayscaleMatrix((float) animation.getAnimatedValue());
210 target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
211 }
212 }, dark, delay, new AnimatorListenerAdapter() {
213 @Override
214 public void onAnimationEnd(Animator animation) {
215 if (!dark) {
216 target.setColorFilter(null);
217 }
218 }
219 });
220 }
221
222 private void updateIconColorFilter(ImageView target, boolean dark) {
223 updateIconColorFilter(target, dark ? 1f : 0f);
224 }
225
226 private void updateIconColorFilter(ImageView target, float intensity) {
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700227 int color = interpolateColor(mColor, mIconDarkColor, intensity);
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100228 mIconColorFilter.setColor(color);
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700229 Drawable iconDrawable = target.getDrawable();
Jorim Jaggidacc9242014-12-08 19:21:26 +0100230
Jorim Jaggi92df1f22014-12-16 19:44:41 +0100231 // The background might be null for legacy notifications. Also, the notification might have
232 // been modified during the animation, so background might be null here.
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700233 if (iconDrawable != null) {
234 iconDrawable.mutate().setColorFilter(mIconColorFilter);
Jorim Jaggidacc9242014-12-08 19:21:26 +0100235 }
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100236 }
237
238 private void updateIconAlpha(ImageView target, boolean dark) {
239 target.setImageAlpha(dark ? mIconDarkAlpha : 255);
240 }
241
242 protected void updateGrayscale(ImageView target, boolean dark) {
243 if (dark) {
244 updateGrayscaleMatrix(1f);
245 target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
246 } else {
247 target.setColorFilter(null);
248 }
249 }
250
Selim Cinek8d6440d2015-10-22 13:00:05 -0700251 @Override
252 public void setSubTextVisible(boolean visible) {
253 if (mSubText == null) {
254 return;
255 }
256 boolean subTextAvailable = !TextUtils.isEmpty(mSubText.getText());
257 if (visible && subTextAvailable) {
258 mSubText.setVisibility(View.VISIBLE);
259 } else {
260 mSubText.setVisibility(View.GONE);
261 }
262 // TODO: figure out what to do with the number (same place as contentInfo)
263 // work profile badge. For now we hide it since it looks nicer.
264 boolean infoAvailable = !TextUtils.isEmpty(mInfoText.getText());
265 if (visible && infoAvailable) {
266 mInfoText.setVisibility(View.VISIBLE);
267 } else {
268 mInfoText.setVisibility(View.GONE);
269 }
270 boolean showThirdLine = (visible && (infoAvailable || subTextAvailable))
271 || mProfileBadge.getVisibility() == View.VISIBLE;
Selim Cinek8d6440d2015-10-22 13:00:05 -0700272 if (mThirdLine != null) {
273 if (showThirdLine) {
274 mThirdLine.setVisibility(View.VISIBLE);
275 } else {
276 mThirdLine.setVisibility(View.GONE);
277 }
278 }
279 }
280
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700281 @Override
282 public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
283 mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
284 mNotificationHeader.setOnTouchListener(expandable ? mHeaderTouchListener : null);
285 mExpandClickListener = onClickListener;
286 }
287
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100288 private void updateGrayscaleMatrix(float intensity) {
289 mGrayscaleColorMatrix.setSaturation(1 - intensity);
290 }
291
292 private static int interpolateColor(int source, int target, float t) {
293 int aSource = Color.alpha(source);
294 int rSource = Color.red(source);
295 int gSource = Color.green(source);
296 int bSource = Color.blue(source);
297 int aTarget = Color.alpha(target);
298 int rTarget = Color.red(target);
299 int gTarget = Color.green(target);
300 int bTarget = Color.blue(target);
301 return Color.argb(
302 (int) (aSource * (1f - t) + aTarget * t),
303 (int) (rSource * (1f - t) + rTarget * t),
304 (int) (gSource * (1f - t) + gTarget * t),
305 (int) (bSource * (1f - t) + bTarget * t));
306 }
Selim Cinek65b2e7c2015-10-26 14:11:31 -0700307
308 public class HeaderTouchListener implements View.OnTouchListener {
309
310 private final ArrayList<Rect> mTouchRects = new ArrayList<>();
311 private int mTouchSlop;
312 private boolean mTrackGesture;
313 private float mDownX;
314 private float mDownY;
315
316 public HeaderTouchListener() {
317 }
318
319 public void bindTouchRects(View parent, View icon, View expandButton) {
320 mTouchRects.clear();
321 addRectAroundViewView(icon);
322 addRectAroundViewView(expandButton);
323 addInBetweenRect(parent);
324 mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
325 }
326
327 private void addInBetweenRect(View parent) {
328 final Rect r = new Rect();
329 r.top = 0;
330 r.bottom = (int) (32 * parent.getResources().getDisplayMetrics().density);
331 Rect leftRect = mTouchRects.get(0);
332 r.left = leftRect.right;
333 Rect rightRect = mTouchRects.get(1);
334 r.right = rightRect.left;
335 mTouchRects.add(r);
336 }
337
338 private void addRectAroundViewView(View view) {
339 final Rect r = getRectAroundView(view);
340 mTouchRects.add(r);
341 }
342
343 private Rect getRectAroundView(View view) {
344 float size = 48 * view.getResources().getDisplayMetrics().density;
345 final Rect r = new Rect();
346 r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f);
347 r.bottom = (int) (r.top + size);
348 r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f);
349 r.right = (int) (r.left + size);
350 return r;
351 }
352
353 @Override
354 public boolean onTouch(View v, MotionEvent event) {
355 float x = event.getX();
356 float y = event.getY();
357 switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
358 case MotionEvent.ACTION_DOWN:
359 mTrackGesture = false;
360 if (isInside(x, y)) {
361 mTrackGesture = true;
362 return true;
363 }
364 break;
365 case MotionEvent.ACTION_MOVE:
366 if (mTrackGesture) {
367 if (Math.abs(mDownX - x) > mTouchSlop
368 || Math.abs(mDownY - y) > mTouchSlop) {
369 mTrackGesture = false;
370 }
371 }
372 break;
373 case MotionEvent.ACTION_UP:
374 if (mTrackGesture) {
375 mExpandClickListener.onClick(mNotificationHeader);
376 }
377 break;
378 }
379 return mTrackGesture;
380 }
381
382 private boolean isInside(float x, float y) {
383 for (int i = 0; i < mTouchRects.size(); i++) {
384 Rect r = mTouchRects.get(i);
385 if (r.contains((int) x, (int) y)) {
386 mDownX = x;
387 mDownY = y;
388 return true;
389 }
390 }
391 return false;
392 }
393 }
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100394}