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