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 android.animation.Animator; 20 import android.animation.AnimatorSet; 21 import android.animation.ObjectAnimator; 22 import android.app.Notification; 23 import android.content.Context; 24 import android.graphics.Rect; 25 import android.support.annotation.Nullable; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.widget.FrameLayout; 30 import android.widget.TextView; 31 32 import com.android.launcher3.ItemInfo; 33 import com.android.launcher3.LauncherAnimUtils; 34 import com.android.launcher3.R; 35 import com.android.launcher3.anim.PropertyResetListener; 36 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 37 import com.android.launcher3.graphics.IconPalette; 38 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider; 39 import com.android.launcher3.popup.PopupItemView; 40 import com.android.launcher3.touch.SwipeDetector; 41 import com.android.launcher3.userevent.nano.LauncherLogProto; 42 import com.android.launcher3.util.Themes; 43 44 import java.util.List; 45 46 /** 47 * A {@link FrameLayout} that contains a header, main view and a footer. 48 * The main view contains the icon and text (title + subtext) of the first notification. 49 * The footer contains: A list of just the icons of all the notifications past the first one. 50 * @see NotificationFooterLayout 51 */ 52 public class NotificationItemView extends PopupItemView implements LogContainerProvider { 53 54 private static final Rect sTempRect = new Rect(); 55 56 private TextView mHeaderText; 57 private TextView mHeaderCount; 58 private NotificationMainView mMainView; 59 private NotificationFooterLayout mFooter; 60 private SwipeDetector mSwipeDetector; 61 private boolean mAnimatingNextIcon; 62 private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT; 63 64 public NotificationItemView(Context context) { 65 this(context, null, 0); 66 } 67 68 public NotificationItemView(Context context, AttributeSet attrs) { 69 this(context, attrs, 0); 70 } 71 72 public NotificationItemView(Context context, AttributeSet attrs, int defStyle) { 73 super(context, attrs, defStyle); 74 } 75 76 @Override 77 protected void onFinishInflate() { 78 super.onFinishInflate(); 79 mHeaderText = findViewById(R.id.notification_text); 80 mHeaderCount = findViewById(R.id.notification_count); 81 mMainView = findViewById(R.id.main_view); 82 mFooter = findViewById(R.id.footer); 83 84 mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL); 85 mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); 86 mMainView.setSwipeDetector(mSwipeDetector); 87 } 88 89 public NotificationMainView getMainView() { 90 return mMainView; 91 } 92 93 /** 94 * This method is used to calculate the height to remove when dismissing the last notification. 95 * We subtract the height of the footer in this case since the footer should be gone or in the 96 * process of being removed. 97 * @return The height of the entire notification item, minus the footer if it still exists. 98 */ 99 public int getHeightMinusFooter() { 100 if (mFooter.getParent() == null) { 101 return getHeight(); 102 } 103 int excessFooterHeight = mFooter.getHeight() - getResources().getDimensionPixelSize( 104 R.dimen.notification_empty_footer_height); 105 return getHeight() - excessFooterHeight; 106 } 107 108 public Animator animateHeightRemoval(int heightToRemove, boolean shouldRemoveFromTop) { 109 AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); 110 111 Rect startRect = new Rect(mPillRect); 112 Rect endRect = new Rect(mPillRect); 113 if (shouldRemoveFromTop) { 114 endRect.top += heightToRemove; 115 } else { 116 endRect.bottom -= heightToRemove; 117 } 118 anim.play(new RoundedRectRevealOutlineProvider(getBackgroundRadius(), getBackgroundRadius(), 119 startRect, endRect, mRoundedCorners).createRevealAnimator(this, false)); 120 121 View bottomGutter = findViewById(R.id.gutter_bottom); 122 if (bottomGutter != null && bottomGutter.getVisibility() == VISIBLE) { 123 Animator translateGutter = ObjectAnimator.ofFloat(bottomGutter, TRANSLATION_Y, 124 -heightToRemove); 125 translateGutter.addListener(new PropertyResetListener<>(TRANSLATION_Y, 0f)); 126 anim.play(translateGutter); 127 } 128 129 return anim; 130 } 131 132 public void updateHeader(int notificationCount, @Nullable IconPalette palette) { 133 mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount)); 134 if (palette != null) { 135 if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) { 136 mNotificationHeaderTextColor = 137 IconPalette.resolveContrastColor(getContext(), palette.dominantColor, 138 Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)); 139 } 140 mHeaderText.setTextColor(mNotificationHeaderTextColor); 141 mHeaderCount.setTextColor(mNotificationHeaderTextColor); 142 } 143 } 144 145 @Override 146 public boolean onInterceptTouchEvent(MotionEvent ev) { 147 if (mMainView.getNotificationInfo() == null) { 148 // The notification hasn't been populated yet. 149 return false; 150 } 151 getParent().requestDisallowInterceptTouchEvent(true); 152 mSwipeDetector.onTouchEvent(ev); 153 return mSwipeDetector.isDraggingOrSettling(); 154 } 155 156 @Override 157 public boolean onTouchEvent(MotionEvent ev) { 158 if (mMainView.getNotificationInfo() == null) { 159 // The notification hasn't been populated yet. 160 return false; 161 } 162 return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev); 163 } 164 165 public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) { 166 if (notificationInfos.isEmpty()) { 167 return; 168 } 169 170 NotificationInfo mainNotification = notificationInfos.get(0); 171 mMainView.applyNotificationInfo(mainNotification, mIconView); 172 173 for (int i = 1; i < notificationInfos.size(); i++) { 174 mFooter.addNotificationInfo(notificationInfos.get(i)); 175 } 176 mFooter.commitNotificationInfos(); 177 } 178 179 public void trimNotifications(final List<String> notificationKeys) { 180 boolean dismissedMainNotification = !notificationKeys.contains( 181 mMainView.getNotificationInfo().notificationKey); 182 if (dismissedMainNotification && !mAnimatingNextIcon) { 183 // Animate the next icon into place as the new main notification. 184 mAnimatingNextIcon = true; 185 mMainView.setVisibility(INVISIBLE); 186 mMainView.setTranslationX(0); 187 mIconView.getGlobalVisibleRect(sTempRect); 188 mFooter.animateFirstNotificationTo(sTempRect, 189 new NotificationFooterLayout.IconAnimationEndListener() { 190 @Override 191 public void onIconAnimationEnd(NotificationInfo newMainNotification) { 192 if (newMainNotification != null) { 193 mMainView.applyNotificationInfo(newMainNotification, mIconView, true); 194 mMainView.setVisibility(VISIBLE); 195 } 196 mAnimatingNextIcon = false; 197 } 198 }); 199 } else { 200 mFooter.trimNotifications(notificationKeys); 201 } 202 } 203 204 @Override 205 public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target, 206 LauncherLogProto.Target targetParent) { 207 target.itemType = LauncherLogProto.ItemType.NOTIFICATION; 208 targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS; 209 } 210 } 211