• Home
  • History
  • Annotate
  • 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.AppOpsManager;
22 import android.app.Notification;
23 import android.content.Context;
24 import android.util.ArraySet;
25 import android.view.NotificationHeaderView;
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.FrameLayout;
31 import android.widget.ImageView;
32 import android.widget.TextView;
33 
34 import com.android.internal.widget.CachingIconView;
35 import com.android.internal.widget.NotificationExpandButton;
36 import com.android.settingslib.Utils;
37 import com.android.systemui.Interpolators;
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.TransformableView;
40 import com.android.systemui.statusbar.ViewTransformationHelper;
41 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
42 import com.android.systemui.statusbar.notification.ImageTransformState;
43 import com.android.systemui.statusbar.notification.NotificationUtils;
44 import com.android.systemui.statusbar.notification.TransformState;
45 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
46 
47 import java.util.Stack;
48 
49 /**
50  * Wraps a notification header view.
51  */
52 public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
53 
54     private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
55             = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
56 
57     protected final ViewTransformationHelper mTransformationHelper;
58 
59     protected int mColor;
60 
61     private CachingIconView mIcon;
62     private NotificationExpandButton mExpandButton;
63     protected NotificationHeaderView mNotificationHeader;
64     private TextView mHeaderText;
65     private TextView mAppNameText;
66     private ImageView mWorkProfileImage;
67     private View mCameraIcon;
68     private View mMicIcon;
69     private View mOverlayIcon;
70     private View mAppOps;
71     private View mAudiblyAlertedIcon;
72     private FrameLayout mIconContainer;
73 
74     private boolean mIsLowPriority;
75     private boolean mTransformLowPriorityTitle;
76     private boolean mShowExpandButtonAtEnd;
77 
NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row)78     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
79         super(ctx, view, row);
80         mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
81                 R.bool.config_showNotificationExpandButtonAtEnd)
82                 || NotificationUtils.useNewInterruptionModel(ctx);
83         mTransformationHelper = new ViewTransformationHelper();
84 
85         // we want to avoid that the header clashes with the other text when transforming
86         // low-priority
87         mTransformationHelper.setCustomTransformation(
88                 new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
89 
90                     @Override
91                     public Interpolator getCustomInterpolator(int interpolationType,
92                             boolean isFrom) {
93                         boolean isLowPriority = mView instanceof NotificationHeaderView;
94                         if (interpolationType == TRANSFORM_Y) {
95                             if (isLowPriority && !isFrom
96                                     || !isLowPriority && isFrom) {
97                                 return Interpolators.LINEAR_OUT_SLOW_IN;
98                             } else {
99                                 return LOW_PRIORITY_HEADER_CLOSE;
100                             }
101                         }
102                         return null;
103                     }
104 
105                     @Override
106                     protected boolean hasCustomTransformation() {
107                         return mIsLowPriority && mTransformLowPriorityTitle;
108                     }
109                 }, TRANSFORMING_VIEW_TITLE);
110         resolveHeaderViews();
111         addAppOpsOnClickListener(row);
112     }
113 
resolveHeaderViews()114     protected void resolveHeaderViews() {
115         mIconContainer = mView.findViewById(com.android.internal.R.id.header_icon_container);
116         mIcon = mView.findViewById(com.android.internal.R.id.icon);
117         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
118         mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
119         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
120         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
121         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
122         mCameraIcon = mView.findViewById(com.android.internal.R.id.camera);
123         mMicIcon = mView.findViewById(com.android.internal.R.id.mic);
124         mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay);
125         mAppOps = mView.findViewById(com.android.internal.R.id.app_ops);
126         mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
127         if (mNotificationHeader != null) {
128             mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
129             mColor = mNotificationHeader.getOriginalIconColor();
130         }
131     }
132 
addAppOpsOnClickListener(ExpandableNotificationRow row)133     private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
134         View.OnClickListener listener = row.getAppOpsOnClickListener();
135         if (mNotificationHeader != null) {
136             mNotificationHeader.setAppOpsOnClickListener(listener);
137         }
138         if (mAppOps != null) {
139             mAppOps.setOnClickListener(listener);
140         }
141     }
142 
143     /**
144      * Shows or hides 'app op in use' icons based on app usage.
145      */
146     @Override
showAppOpsIcons(ArraySet<Integer> appOps)147     public void showAppOpsIcons(ArraySet<Integer> appOps) {
148         if (appOps == null) {
149             return;
150         }
151         if (mOverlayIcon != null) {
152             mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
153                     ? View.VISIBLE : View.GONE);
154         }
155         if (mCameraIcon != null) {
156             mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
157                     ? View.VISIBLE : View.GONE);
158         }
159         if (mMicIcon != null) {
160             mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
161                     ? View.VISIBLE : View.GONE);
162         }
163     }
164 
165     @Override
onContentUpdated(ExpandableNotificationRow row)166     public void onContentUpdated(ExpandableNotificationRow row) {
167         super.onContentUpdated(row);
168         mIsLowPriority = row.getEntry().isAmbient();
169         mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
170         ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
171 
172         // Reinspect the notification.
173         resolveHeaderViews();
174         updateTransformedTypes();
175         addRemainingTransformTypes();
176         updateCropToPaddingForImageViews();
177         Notification notification = row.getEntry().getSbn().getNotification();
178         mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
179 
180         // We need to reset all views that are no longer transforming in case a view was previously
181         // transformed, but now we decided to transform its container instead.
182         ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews();
183         for (int i = 0; i < previousViews.size(); i++) {
184             View view = previousViews.valueAt(i);
185             if (!currentViews.contains(view)) {
186                 mTransformationHelper.resetTransformedView(view);
187             }
188         }
189     }
190 
applyConversationSkin()191     public void applyConversationSkin() {
192         if (mAppNameText != null) {
193             mAppNameText.setTextAppearance(
194                     com.android.internal.R.style
195                             .TextAppearance_DeviceDefault_Notification_Conversation_AppName);
196             ViewGroup.MarginLayoutParams layoutParams =
197                     (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
198             layoutParams.setMarginStart(0);
199         }
200         if (mIconContainer != null) {
201             ViewGroup.MarginLayoutParams layoutParams =
202                     (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
203             layoutParams.width =
204                     mIconContainer.getContext().getResources().getDimensionPixelSize(
205                             com.android.internal.R.dimen.conversation_content_start);
206             final int marginStart =
207                     mIconContainer.getContext().getResources().getDimensionPixelSize(
208                             com.android.internal.R.dimen.notification_content_margin_start);
209             layoutParams.setMarginStart(marginStart * -1);
210         }
211         if (mIcon != null) {
212             ViewGroup.MarginLayoutParams layoutParams =
213                     (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
214             layoutParams.setMarginEnd(0);
215         }
216     }
217 
clearConversationSkin()218     public void clearConversationSkin() {
219         if (mAppNameText != null) {
220             final int textAppearance = Utils.getThemeAttr(
221                     mAppNameText.getContext(),
222                     com.android.internal.R.attr.notificationHeaderTextAppearance,
223                     com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
224             mAppNameText.setTextAppearance(textAppearance);
225             ViewGroup.MarginLayoutParams layoutParams =
226                     (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
227             final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize(
228                     com.android.internal.R.dimen.notification_header_app_name_margin_start);
229             layoutParams.setMarginStart(marginStart);
230         }
231         if (mIconContainer != null) {
232             ViewGroup.MarginLayoutParams layoutParams =
233                     (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
234             layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
235             layoutParams.setMarginStart(0);
236         }
237         if (mIcon != null) {
238             ViewGroup.MarginLayoutParams layoutParams =
239                     (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
240             final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
241                     com.android.internal.R.dimen.notification_header_icon_margin_end);
242             layoutParams.setMarginEnd(marginEnd);
243         }
244     }
245 
246     /**
247      * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
248      * child is faded automatically and doesn't have to be manually added.
249      * The keys used for the views are the ids.
250      */
addRemainingTransformTypes()251     private void addRemainingTransformTypes() {
252         mTransformationHelper.addRemainingTransformTypes(mView);
253     }
254 
255     /**
256      * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
257      * anymore during these transitions. We can avoid that by using
258      * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
259      */
updateCropToPaddingForImageViews()260     private void updateCropToPaddingForImageViews() {
261         Stack<View> stack = new Stack<>();
262         stack.push(mView);
263         while (!stack.isEmpty()) {
264             View child = stack.pop();
265             if (child instanceof ImageView
266                     // Skip the importance ring for conversations, disabled cropping is needed for
267                     // its animation
268                     && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
269                 ((ImageView) child).setCropToPadding(true);
270             } else if (child instanceof ViewGroup){
271                 ViewGroup group = (ViewGroup) child;
272                 for (int i = 0; i < group.getChildCount(); i++) {
273                     stack.push(group.getChildAt(i));
274                 }
275             }
276         }
277     }
278 
updateTransformedTypes()279     protected void updateTransformedTypes() {
280         mTransformationHelper.reset();
281         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON,
282                 mIcon);
283         mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
284         if (mIsLowPriority && mHeaderText != null) {
285             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
286                     mHeaderText);
287         }
288         if (mCameraIcon != null) {
289             mTransformationHelper.addViewTransformingToSimilar(mCameraIcon);
290         }
291         if (mMicIcon != null) {
292             mTransformationHelper.addViewTransformingToSimilar(mMicIcon);
293         }
294         if (mOverlayIcon != null) {
295             mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon);
296         }
297         if (mAudiblyAlertedIcon != null) {
298             mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
299         }
300     }
301 
302     @Override
updateExpandability(boolean expandable, View.OnClickListener onClickListener)303     public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
304         mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
305         if (mNotificationHeader != null) {
306             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
307         }
308     }
309 
310     @Override
setRecentlyAudiblyAlerted(boolean audiblyAlerted)311     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
312         if (mAudiblyAlertedIcon != null) {
313             mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
314         }
315     }
316 
317     @Override
getNotificationHeader()318     public NotificationHeaderView getNotificationHeader() {
319         return mNotificationHeader;
320     }
321 
322     @Override
getExpandButton()323     public View getExpandButton() {
324         return mExpandButton;
325     }
326 
327     @Override
getOriginalIconColor()328     public int getOriginalIconColor() {
329         return mIcon.getOriginalIconColor();
330     }
331 
332     @Override
getShelfTransformationTarget()333     public View getShelfTransformationTarget() {
334         return mIcon;
335     }
336 
337     @Override
setShelfIconVisible(boolean visible)338     public void setShelfIconVisible(boolean visible) {
339         super.setShelfIconVisible(visible);
340         mIcon.setForceHidden(visible);
341     }
342 
343     @Override
getCurrentState(int fadingView)344     public TransformState getCurrentState(int fadingView) {
345         return mTransformationHelper.getCurrentState(fadingView);
346     }
347 
348     @Override
transformTo(TransformableView notification, Runnable endRunnable)349     public void transformTo(TransformableView notification, Runnable endRunnable) {
350         mTransformationHelper.transformTo(notification, endRunnable);
351     }
352 
353     @Override
transformTo(TransformableView notification, float transformationAmount)354     public void transformTo(TransformableView notification, float transformationAmount) {
355         mTransformationHelper.transformTo(notification, transformationAmount);
356     }
357 
358     @Override
transformFrom(TransformableView notification)359     public void transformFrom(TransformableView notification) {
360         mTransformationHelper.transformFrom(notification);
361     }
362 
363     @Override
transformFrom(TransformableView notification, float transformationAmount)364     public void transformFrom(TransformableView notification, float transformationAmount) {
365         mTransformationHelper.transformFrom(notification, transformationAmount);
366     }
367 
368     @Override
setIsChildInGroup(boolean isChildInGroup)369     public void setIsChildInGroup(boolean isChildInGroup) {
370         super.setIsChildInGroup(isChildInGroup);
371         mTransformLowPriorityTitle = !isChildInGroup;
372     }
373 
374     @Override
setVisible(boolean visible)375     public void setVisible(boolean visible) {
376         super.setVisible(visible);
377         mTransformationHelper.setVisible(visible);
378     }
379 }
380