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