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 
removeFooter()89     public void removeFooter() {
90         if (mContainer.indexOfChild(mFooter) >= 0) {
91             mContainer.removeView(mFooter);
92             mContainer.removeView(mDivider);
93         }
94     }
95 
inverseGutterMargin()96     public void inverseGutterMargin() {
97         MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
98         int top = lp.topMargin;
99         lp.topMargin = lp.bottomMargin;
100         lp.bottomMargin = top;
101     }
102 
removeAllViews()103     public void removeAllViews() {
104         mContainer.removeView(mMainView);
105         mContainer.removeView(mHeader);
106 
107         if (mContainer.indexOfChild(mFooter) >= 0) {
108             mContainer.removeView(mFooter);
109             mContainer.removeView(mDivider);
110         }
111 
112         if (mGutter != null) {
113             mContainer.removeView(mGutter);
114         }
115     }
116 
updateHeader(int notificationCount, int iconColor)117     public void updateHeader(int notificationCount, int iconColor) {
118         mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
119         if (Color.alpha(iconColor) > 0) {
120             if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
121                 mNotificationHeaderTextColor =
122                         IconPalette.resolveContrastColor(mContext, iconColor,
123                                 Themes.getAttrColor(mContext, R.attr.popupColorPrimary));
124             }
125             mHeaderText.setTextColor(mNotificationHeaderTextColor);
126             mHeaderCount.setTextColor(mNotificationHeaderTextColor);
127         }
128     }
129 
onInterceptTouchEvent(MotionEvent ev)130     public boolean onInterceptTouchEvent(MotionEvent ev) {
131         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
132             sTempRect.set(mMainView.getLeft(), mMainView.getTop(),
133                     mMainView.getRight(), mMainView.getBottom());
134             mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
135             if (!mIgnoreTouch) {
136                 mContainer.getParent().requestDisallowInterceptTouchEvent(true);
137             }
138         }
139         if (mIgnoreTouch) {
140             return false;
141         }
142         if (mMainView.getNotificationInfo() == null) {
143             // The notification hasn't been populated yet.
144             return false;
145         }
146 
147         mSwipeDetector.onTouchEvent(ev);
148         return mSwipeDetector.isDraggingOrSettling();
149     }
150 
onTouchEvent(MotionEvent ev)151     public boolean onTouchEvent(MotionEvent ev) {
152         if (mIgnoreTouch) {
153             return false;
154         }
155         if (mMainView.getNotificationInfo() == null) {
156             // The notification hasn't been populated yet.
157             return false;
158         }
159         return mSwipeDetector.onTouchEvent(ev);
160     }
161 
applyNotificationInfos(final List<NotificationInfo> notificationInfos)162     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
163         if (notificationInfos.isEmpty()) {
164             return;
165         }
166 
167         NotificationInfo mainNotification = notificationInfos.get(0);
168         mMainView.applyNotificationInfo(mainNotification, false);
169 
170         for (int i = 1; i < notificationInfos.size(); i++) {
171             mFooter.addNotificationInfo(notificationInfos.get(i));
172         }
173         mFooter.commitNotificationInfos();
174     }
175 
trimNotifications(final List<String> notificationKeys)176     public void trimNotifications(final List<String> notificationKeys) {
177         boolean dismissedMainNotification = !notificationKeys.contains(
178                 mMainView.getNotificationInfo().notificationKey);
179         if (dismissedMainNotification && !mAnimatingNextIcon) {
180             // Animate the next icon into place as the new main notification.
181             mAnimatingNextIcon = true;
182             mMainView.setContentVisibility(View.INVISIBLE);
183             mMainView.setContentTranslation(0);
184             mIconView.getGlobalVisibleRect(sTempRect);
185             mFooter.animateFirstNotificationTo(sTempRect, (newMainNotification) -> {
186                 if (newMainNotification != null) {
187                     mMainView.applyNotificationInfo(newMainNotification, true);
188                     mMainView.setContentVisibility(View.VISIBLE);
189                 }
190                 mAnimatingNextIcon = false;
191             });
192         } else {
193             mFooter.trimNotifications(notificationKeys);
194         }
195     }
196 }
197