1 /*
2  * Copyright (C) 2017 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.launcher3.notification;
18 
19 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
20 
21 import android.app.Notification;
22 import android.content.Context;
23 import android.graphics.Color;
24 import android.graphics.Rect;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewGroup.MarginLayoutParams;
28 import android.widget.TextView;
29 
30 import com.android.launcher3.R;
31 import com.android.launcher3.graphics.IconPalette;
32 import com.android.launcher3.popup.PopupContainerWithArrow;
33 import com.android.launcher3.touch.SingleAxisSwipeDetector;
34 import com.android.launcher3.util.Themes;
35 
36 import java.util.List;
37 
38 /**
39  * Utility class to manage notification UI
40  */
41 public class NotificationItemView {
42 
43     private static final Rect sTempRect = new Rect();
44 
45     private final Context mContext;
46     private final PopupContainerWithArrow mContainer;
47 
48     private final TextView mHeaderText;
49     private final TextView mHeaderCount;
50     private final NotificationMainView mMainView;
51     private final NotificationFooterLayout mFooter;
52     private final SingleAxisSwipeDetector mSwipeDetector;
53     private final View mIconView;
54 
55     private final View mHeader;
56     private final View mDivider;
57 
58     private View mGutter;
59 
60     private boolean mIgnoreTouch = false;
61     private boolean mAnimatingNextIcon;
62     private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
63 
NotificationItemView(PopupContainerWithArrow container)64     public NotificationItemView(PopupContainerWithArrow container) {
65         mContainer = container;
66         mContext = container.getContext();
67 
68         mHeaderText = container.findViewById(R.id.notification_text);
69         mHeaderCount = container.findViewById(R.id.notification_count);
70         mMainView = container.findViewById(R.id.main_view);
71         mFooter = container.findViewById(R.id.footer);
72         mIconView = container.findViewById(R.id.popup_item_icon);
73 
74         mHeader = container.findViewById(R.id.header);
75         mDivider = container.findViewById(R.id.divider);
76 
77         mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
78         mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
79         mMainView.setSwipeDetector(mSwipeDetector);
80         mFooter.setContainer(this);
81     }
82 
addGutter()83     public void addGutter() {
84         if (mGutter == null) {
85             mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter, mContainer);
86         }
87     }
88 
89     /**
90      * Sets width for notification footer and spaces out items evenly
91      */
setFooterWidth(int footerWidth)92     public void setFooterWidth(int footerWidth) {
93         mFooter.setWidth(footerWidth);
94     }
95 
removeFooter()96     public void removeFooter() {
97         if (mContainer.indexOfChild(mFooter) >= 0) {
98             mContainer.removeView(mFooter);
99             mContainer.removeView(mDivider);
100         }
101     }
102 
inverseGutterMargin()103     public void inverseGutterMargin() {
104         MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
105         int top = lp.topMargin;
106         lp.topMargin = lp.bottomMargin;
107         lp.bottomMargin = top;
108     }
109 
removeAllViews()110     public void removeAllViews() {
111         mContainer.removeView(mMainView);
112         mContainer.removeView(mHeader);
113 
114         if (mContainer.indexOfChild(mFooter) >= 0) {
115             mContainer.removeView(mFooter);
116             mContainer.removeView(mDivider);
117         }
118 
119         if (mGutter != null) {
120             mContainer.removeView(mGutter);
121         }
122     }
123 
updateHeader(int notificationCount, int iconColor)124     public void updateHeader(int notificationCount, int iconColor) {
125         mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
126         if (Color.alpha(iconColor) > 0) {
127             if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
128                 mNotificationHeaderTextColor =
129                         IconPalette.resolveContrastColor(mContext, iconColor,
130                                 Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
131             }
132             mHeaderText.setTextColor(mNotificationHeaderTextColor);
133             mHeaderCount.setTextColor(mNotificationHeaderTextColor);
134         }
135     }
136 
onInterceptTouchEvent(MotionEvent ev)137     public boolean onInterceptTouchEvent(MotionEvent ev) {
138         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
139             sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
140                     mMainView.getRight(), mMainView.getBottom());
141             mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
142             if (!mIgnoreTouch) {
143                 mContainer.getParent().requestDisallowInterceptTouchEvent(true);
144             }
145         }
146         if (mIgnoreTouch) {
147             return false;
148         }
149         if (mMainView.getNotificationInfo() == null) {
150             // The notification hasn't been populated yet.
151             return false;
152         }
153 
154         mSwipeDetector.onTouchEvent(ev);
155         return mSwipeDetector.isDraggingOrSettling();
156     }
157 
onTouchEvent(MotionEvent ev)158     public boolean onTouchEvent(MotionEvent ev) {
159         if (mIgnoreTouch) {
160             return false;
161         }
162         if (mMainView.getNotificationInfo() == null) {
163             // The notification hasn't been populated yet.
164             return false;
165         }
166         return mSwipeDetector.onTouchEvent(ev);
167     }
168 
applyNotificationInfos(final List<NotificationInfo> notificationInfos)169     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
170         if (notificationInfos.isEmpty()) {
171             return;
172         }
173 
174         NotificationInfo mainNotification = notificationInfos.get(0);
175         mMainView.applyNotificationInfo(mainNotification, false);
176 
177         for (int i = 1; i < notificationInfos.size(); i++) {
178             mFooter.addNotificationInfo(notificationInfos.get(i));
179         }
180         mFooter.commitNotificationInfos();
181     }
182 
trimNotifications(final List<String> notificationKeys)183     public void trimNotifications(final List<String> notificationKeys) {
184         boolean dismissedMainNotification = !notificationKeys.contains(
185                 mMainView.getNotificationInfo().notificationKey);
186         if (dismissedMainNotification && !mAnimatingNextIcon) {
187             // Animate the next icon into place as the new main notification.
188             mAnimatingNextIcon = true;
189             mMainView.setContentVisibility(View.INVISIBLE);
190             mMainView.setContentTranslation(0);
191             mIconView.getGlobalVisibleRect(sTempRect);
192             mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
193                 if (newMainNotification != null) {
194                     mMainView.applyNotificationInfo(newMainNotification, true);
195                     mMainView.setContentVisibility(View.VISIBLE);
196                 }
197                 mAnimatingNextIcon = false;
198             });
199         } else {
200             mFooter.trimNotifications(notificationKeys);
201         }
202     }
203 }
204