• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.systemui.statusbar.notification.row.wrapper;
18 
19 import static android.view.View.GONE;
20 import static android.view.View.VISIBLE;
21 
22 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
23 
24 import android.app.Notification;
25 import android.content.Context;
26 import android.util.ArraySet;
27 import android.view.NotificationHeaderView;
28 import android.view.NotificationTopLineView;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.animation.Interpolator;
32 import android.view.animation.PathInterpolator;
33 import android.widget.DateTimeView;
34 import android.widget.ImageButton;
35 import android.widget.ImageView;
36 import android.widget.TextView;
37 
38 import androidx.annotation.Nullable;
39 
40 import com.android.app.animation.Interpolators;
41 import com.android.internal.widget.CachingIconView;
42 import com.android.internal.widget.NotificationCloseButton;
43 import com.android.internal.widget.NotificationExpandButton;
44 import com.android.systemui.res.R;
45 import com.android.systemui.statusbar.TransformableView;
46 import com.android.systemui.statusbar.ViewTransformationHelper;
47 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
48 import com.android.systemui.statusbar.notification.FeedbackIcon;
49 import com.android.systemui.statusbar.notification.ImageTransformState;
50 import com.android.systemui.statusbar.notification.Roundable;
51 import com.android.systemui.statusbar.notification.RoundableState;
52 import com.android.systemui.statusbar.notification.TransformState;
53 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
54 import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
55 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
56 
57 import java.util.Stack;
58 
59 /**
60  * Wraps a notification view which may or may not include a header.
61  */
62 public class NotificationHeaderViewWrapper extends NotificationViewWrapper implements Roundable {
63 
64     private final RoundableState mRoundableState;
65     private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
66             = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
67     protected final ViewTransformationHelper mTransformationHelper;
68     private CachingIconView mIcon;
69     private NotificationCloseButton mCloseButton;
70     private NotificationExpandButton mExpandButton;
71     private View mAltExpandTarget;
72     private View mIconContainer;
73     protected NotificationHeaderView mNotificationHeader;
74     protected NotificationTopLineView mNotificationTopLine;
75     private TextView mHeaderText;
76     private TextView mAppNameText;
77     private ImageView mWorkProfileImage;
78     private View mAudiblyAlertedIcon;
79     private View mFeedbackIcon;
80     private boolean mIsLowPriority;
81     private boolean mTransformLowPriorityTitle;
82     private RoundnessChangedListener mRoundnessChangedListener;
83 
NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row)84     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
85         super(ctx, view, row);
86         mRoundableState = new RoundableState(
87                 mView,
88                 this,
89                 ctx.getResources().getDimension(R.dimen.notification_corner_radius)
90         );
91         mTransformationHelper = new ViewTransformationHelper();
92 
93         // we want to avoid that the header clashes with the other text when transforming
94         // low-priority
95         mTransformationHelper.setCustomTransformation(
96                 new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
97 
98                     @Override
99                     public Interpolator getCustomInterpolator(
100                             int interpolationType,
101                             boolean isFrom) {
102                         boolean isLowPriority = mView instanceof NotificationHeaderView;
103                         if (interpolationType == TRANSFORM_Y) {
104                             if (isLowPriority && !isFrom
105                                     || !isLowPriority && isFrom) {
106                                 return Interpolators.LINEAR_OUT_SLOW_IN;
107                             } else {
108                                 return LOW_PRIORITY_HEADER_CLOSE;
109                             }
110                         }
111                         return null;
112                     }
113 
114                     @Override
115                     protected boolean hasCustomTransformation() {
116                         return mIsLowPriority && mTransformLowPriorityTitle;
117                     }
118                 },
119                 TRANSFORMING_VIEW_TITLE);
120         resolveHeaderViews();
121         addFeedbackOnClickListener(row);
122         addCloseButtonOnClickListener(row);
123 
124         if (NotificationAddXOnHoverToDismiss.isEnabled()) {
125             mRow.addDismissButtonTargetStateListener(mHoverListener);
126         }
127     }
128 
129     @Override
getRoundableState()130     public RoundableState getRoundableState() {
131         return mRoundableState;
132     }
133 
134     @Override
getClipHeight()135     public int getClipHeight() {
136         return mView.getHeight();
137     }
138 
139     @Override
applyRoundnessAndInvalidate()140     public void applyRoundnessAndInvalidate() {
141         if (mRoundnessChangedListener != null) {
142             // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
143             // clip our canvas. So we should invalidate our parent.
144             mRoundnessChangedListener.applyRoundnessAndInvalidate();
145         }
146         Roundable.super.applyRoundnessAndInvalidate();
147     }
148 
setOnRoundnessChangedListener(RoundnessChangedListener listener)149     public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
150         mRoundnessChangedListener = listener;
151     }
152 
resolveHeaderViews()153     protected void resolveHeaderViews() {
154         mIcon = mView.findViewById(com.android.internal.R.id.icon);
155         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
156         mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
157         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
158         mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
159         mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
160         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
161         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
162         mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line);
163         mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
164         mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
165         mCloseButton = mView.findViewById(com.android.internal.R.id.close_button);
166     }
167 
addFeedbackOnClickListener(ExpandableNotificationRow row)168     private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
169         View.OnClickListener listener = row.getFeedbackOnClickListener();
170         if (mNotificationTopLine != null) {
171             mNotificationTopLine.setFeedbackOnClickListener(listener);
172         }
173         if (mFeedbackIcon != null) {
174             mFeedbackIcon.setOnClickListener(listener);
175         }
176     }
177 
178     private ExpandableNotificationRow.DismissButtonTargetVisibilityListener mHoverListener = new
179             ExpandableNotificationRow.DismissButtonTargetVisibilityListener() {
180                 @Override
181                 public void onTargetVisibilityChanged(boolean targetVisible) {
182                     NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
183 
184                     if (mCloseButton != null) {
185                         mCloseButton.setVisibility(targetVisible ? VISIBLE : GONE);
186                     }
187                 }
188             };
189 
190     @Override
setRemoved()191     public void setRemoved() {
192         super.setRemoved();
193 
194         if (NotificationAddXOnHoverToDismiss.isEnabled()) {
195             mRow.removeDismissButtonTargetStateListener(mHoverListener);
196         }
197     }
198 
199     /**
200      * Shows the given feedback icon, or hides the icon if null.
201      */
202     @Override
setFeedbackIcon(@ullable FeedbackIcon icon)203     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
204         if (mFeedbackIcon != null) {
205             mFeedbackIcon.setVisibility(icon != null ? VISIBLE : GONE);
206             if (icon != null) {
207                 if (mFeedbackIcon instanceof ImageButton) {
208                     ((ImageButton) mFeedbackIcon).setImageResource(icon.getIconRes());
209                 }
210                 mFeedbackIcon.setContentDescription(
211                         mView.getContext().getString(icon.getContentDescRes()));
212             }
213         }
214     }
215 
addCloseButtonOnClickListener(ExpandableNotificationRow row)216     private void addCloseButtonOnClickListener(ExpandableNotificationRow row) {
217         View.OnClickListener listener = row.getCloseButtonOnClickListener(row);
218         if (mCloseButton != null && listener != null) {
219             mCloseButton.setOnClickListener(listener);
220         }
221     }
222 
223     @Override
onContentUpdated(ExpandableNotificationRow row)224     public void onContentUpdated(ExpandableNotificationRow row) {
225         super.onContentUpdated(row);
226         mIsLowPriority = NotificationBundleUi.isEnabled()
227                 ? row.getEntryAdapter().isAmbient()
228                 : row.getEntryLegacy().isAmbient();
229         mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
230         ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
231 
232         // Reinspect the notification.
233         resolveHeaderViews();
234         updateTransformedTypes();
235         addRemainingTransformTypes();
236         updateCropToPaddingForImageViews();
237         Notification n = NotificationBundleUi.isEnabled()
238                 ? row.getEntryAdapter().getSbn().getNotification()
239                 : row.getEntryLegacy().getSbn().getNotification();
240         mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
241 
242         // We need to reset all views that are no longer transforming in case a view was previously
243         // transformed, but now we decided to transform its container instead.
244         ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews();
245         for (int i = 0; i < previousViews.size(); i++) {
246             View view = previousViews.valueAt(i);
247             if (!currentViews.contains(view)) {
248                 mTransformationHelper.resetTransformedView(view);
249             }
250         }
251     }
252 
253     /**
254      * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
255      * child is faded automatically and doesn't have to be manually added.
256      * The keys used for the views are the ids.
257      */
addRemainingTransformTypes()258     private void addRemainingTransformTypes() {
259         mTransformationHelper.addRemainingTransformTypes(mView);
260     }
261 
262     /**
263      * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
264      * anymore during these transitions. We can avoid that by using
265      * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
266      */
updateCropToPaddingForImageViews()267     private void updateCropToPaddingForImageViews() {
268         Stack<View> stack = new Stack<>();
269         stack.push(mView);
270         while (!stack.isEmpty()) {
271             View child = stack.pop();
272             if (child instanceof ImageView
273                     // Skip the importance ring for conversations, disabled cropping is needed for
274                     // its animation
275                     && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
276                 ((ImageView) child).setCropToPadding(true);
277             } else if (child instanceof ViewGroup) {
278                 ViewGroup group = (ViewGroup) child;
279                 for (int i = 0; i < group.getChildCount(); i++) {
280                     stack.push(group.getChildAt(i));
281                 }
282             }
283         }
284     }
285 
updateTransformedTypes()286     protected void updateTransformedTypes() {
287         mTransformationHelper.reset();
288         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
289         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
290                 mExpandButton);
291         if (mIsLowPriority && mHeaderText != null) {
292             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
293                     mHeaderText);
294         }
295         addViewsTransformingToSimilar(mWorkProfileImage, mAudiblyAlertedIcon, mFeedbackIcon);
296     }
297 
298     @Override
updateExpandability( boolean expandable, View.OnClickListener onClickListener, boolean requestLayout)299     public void updateExpandability(
300             boolean expandable,
301             View.OnClickListener onClickListener,
302             boolean requestLayout) {
303         mExpandButton.setVisibility(expandable ? VISIBLE : GONE);
304         mExpandButton.setOnClickListener(expandable ? onClickListener : null);
305         if (mAltExpandTarget != null) {
306             mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
307         }
308         if (mIconContainer != null) {
309             mIconContainer.setOnClickListener(expandable ? onClickListener : null);
310         }
311         if (mNotificationHeader != null) {
312             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
313         }
314         // Unfortunately, the NotificationContentView has to layout its children in order to
315         // determine their heights, and that affects the button visibility.  If that happens
316         // (thankfully it is rare) then we need to request layout of the expand button's parent
317         // in order to ensure it gets laid out correctly.
318         if (requestLayout) {
319             mExpandButton.getParent().requestLayout();
320         }
321     }
322 
323     @Override
setExpanded(boolean expanded)324     public void setExpanded(boolean expanded) {
325         mExpandButton.setExpanded(expanded);
326     }
327 
328     @Override
setRecentlyAudiblyAlerted(boolean audiblyAlerted)329     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
330         if (mAudiblyAlertedIcon != null) {
331             mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? VISIBLE : GONE);
332         }
333     }
334 
335     @Override
getNotificationHeader()336     public NotificationHeaderView getNotificationHeader() {
337         return mNotificationHeader;
338     }
339 
340     @Override
getExpandButton()341     public View getExpandButton() {
342         return mExpandButton;
343     }
344 
345     @Override
getIcon()346     public CachingIconView getIcon() {
347         return mIcon;
348     }
349 
350     @Override
getOriginalIconColor()351     public int getOriginalIconColor() {
352         return mIcon.getOriginalIconColor();
353     }
354 
355     @Override
getShelfTransformationTarget()356     public View getShelfTransformationTarget() {
357         return mIcon;
358     }
359 
360     @Override
getCurrentState(int fadingView)361     public TransformState getCurrentState(int fadingView) {
362         return mTransformationHelper.getCurrentState(fadingView);
363     }
364 
365     @Override
transformTo(TransformableView notification, Runnable endRunnable)366     public void transformTo(TransformableView notification, Runnable endRunnable) {
367         mTransformationHelper.transformTo(notification, endRunnable);
368     }
369 
370     @Override
transformTo(TransformableView notification, float transformationAmount)371     public void transformTo(TransformableView notification, float transformationAmount) {
372         mTransformationHelper.transformTo(notification, transformationAmount);
373     }
374 
375     @Override
transformFrom(TransformableView notification)376     public void transformFrom(TransformableView notification) {
377         mTransformationHelper.transformFrom(notification);
378     }
379 
380     @Override
transformFrom(TransformableView notification, float transformationAmount)381     public void transformFrom(TransformableView notification, float transformationAmount) {
382         mTransformationHelper.transformFrom(notification, transformationAmount);
383     }
384 
385     @Override
setIsChildInGroup(boolean isChildInGroup)386     public void setIsChildInGroup(boolean isChildInGroup) {
387         super.setIsChildInGroup(isChildInGroup);
388         mTransformLowPriorityTitle = !isChildInGroup;
389     }
390 
391     @Override
setVisible(boolean visible)392     public void setVisible(boolean visible) {
393         super.setVisible(visible);
394         mTransformationHelper.setVisible(visible);
395     }
396 
397     /***
398      * Set Notification when value
399      * @param whenMillis
400      */
setNotificationWhen(long whenMillis)401     public void setNotificationWhen(long whenMillis) {
402         final View timeView = mView.findViewById(com.android.internal.R.id.time);
403 
404         if (timeView instanceof DateTimeView) {
405             ((DateTimeView) timeView).setTime(whenMillis);
406         }
407     }
408 
addTransformedViews(View... views)409     protected void addTransformedViews(View... views) {
410         for (View view : views) {
411             if (view != null) {
412                 mTransformationHelper.addTransformedView(view);
413             }
414         }
415     }
416 
addViewsTransformingToSimilar(View... views)417     protected void addViewsTransformingToSimilar(View... views) {
418         for (View view : views) {
419             if (view != null) {
420                 mTransformationHelper.addViewTransformingToSimilar(view);
421             }
422         }
423     }
424 
425     /**
426      * Interface that handle the Roundness changes
427      */
428     public interface RoundnessChangedListener {
429         /**
430          * This method will be called when this class call applyRoundnessAndInvalidate()
431          */
applyRoundnessAndInvalidate()432         void applyRoundnessAndInvalidate();
433     }
434 }
435