• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.view.Gravity;
28 import android.view.View;
29 import android.widget.FrameLayout;
30 import android.widget.LinearLayout;
31 
32 import com.android.launcher3.LauncherAnimUtils;
33 import com.android.launcher3.R;
34 import com.android.launcher3.Utilities;
35 import com.android.launcher3.anim.PropertyListBuilder;
36 import com.android.launcher3.anim.PropertyResetListener;
37 import com.android.launcher3.util.Themes;
38 
39 import java.util.ArrayList;
40 import java.util.Iterator;
41 import java.util.List;
42 
43 /**
44  * A {@link FrameLayout} that contains only icons of notifications.
45  * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
46  */
47 public class NotificationFooterLayout extends FrameLayout {
48 
49     public interface IconAnimationEndListener {
onIconAnimationEnd(NotificationInfo animatedNotification)50         void onIconAnimationEnd(NotificationInfo animatedNotification);
51     }
52 
53     private static final int MAX_FOOTER_NOTIFICATIONS = 5;
54 
55     private static final Rect sTempRect = new Rect();
56 
57     private final List<NotificationInfo> mNotifications = new ArrayList<>();
58     private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
59     private final boolean mRtl;
60     private final int mBackgroundColor;
61 
62     FrameLayout.LayoutParams mIconLayoutParams;
63     private View mOverflowEllipsis;
64     private LinearLayout mIconRow;
65     private NotificationItemView mContainer;
66 
NotificationFooterLayout(Context context)67     public NotificationFooterLayout(Context context) {
68         this(context, null, 0);
69     }
70 
NotificationFooterLayout(Context context, AttributeSet attrs)71     public NotificationFooterLayout(Context context, AttributeSet attrs) {
72         this(context, attrs, 0);
73     }
74 
NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle)75     public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
76         super(context, attrs, defStyle);
77 
78         Resources res = getResources();
79         mRtl = Utilities.isRtl(res);
80 
81         int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
82         mIconLayoutParams = new LayoutParams(iconSize, iconSize);
83         mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
84         // Compute margin start for each icon such that the icons between the first one
85         // and the ellipsis are evenly spaced out.
86         int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
87         int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
88                 + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
89         int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
90         int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
91                 - iconSize * MAX_FOOTER_NOTIFICATIONS;
92         mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
93 
94         mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
95     }
96 
97     @Override
onFinishInflate()98     protected void onFinishInflate() {
99         super.onFinishInflate();
100         mOverflowEllipsis = findViewById(R.id.overflow);
101         mIconRow = findViewById(R.id.icon_row);
102     }
103 
setContainer(NotificationItemView container)104     void setContainer(NotificationItemView container) {
105         mContainer = container;
106     }
107 
108     /**
109      * Keep track of the NotificationInfo, and then update the UI when
110      * {@link #commitNotificationInfos()} is called.
111      */
addNotificationInfo(final NotificationInfo notificationInfo)112     public void addNotificationInfo(final NotificationInfo notificationInfo) {
113         if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
114             mNotifications.add(notificationInfo);
115         } else {
116             mOverflowNotifications.add(notificationInfo);
117         }
118     }
119 
120     /**
121      * Adds icons and potentially overflow text for all of the NotificationInfo's
122      * added using {@link #addNotificationInfo(NotificationInfo)}.
123      */
commitNotificationInfos()124     public void commitNotificationInfos() {
125         mIconRow.removeAllViews();
126 
127         for (int i = 0; i < mNotifications.size(); i++) {
128             NotificationInfo info = mNotifications.get(i);
129             addNotificationIconForInfo(info);
130         }
131         updateOverflowEllipsisVisibility();
132     }
133 
updateOverflowEllipsisVisibility()134     private void updateOverflowEllipsisVisibility() {
135         mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
136     }
137 
138     /**
139      * Creates an icon for the given NotificationInfo, and adds it to the icon row.
140      * @return the icon view that was added
141      */
addNotificationIconForInfo(NotificationInfo info)142     private View addNotificationIconForInfo(NotificationInfo info) {
143         View icon = new View(getContext());
144         icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
145         icon.setOnClickListener(info);
146         icon.setTag(info);
147         icon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
148         mIconRow.addView(icon, 0, mIconLayoutParams);
149         return icon;
150     }
151 
animateFirstNotificationTo(Rect toBounds, final IconAnimationEndListener callback)152     public void animateFirstNotificationTo(Rect toBounds,
153             final IconAnimationEndListener callback) {
154         AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
155         final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
156 
157         Rect fromBounds = sTempRect;
158         firstNotification.getGlobalVisibleRect(fromBounds);
159         float scale = (float) toBounds.height() / fromBounds.height();
160         Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
161                 new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
162                         + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
163         moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
164             @Override
165             public void onAnimationEnd(Animator animation) {
166                 callback.onIconAnimationEnd((NotificationInfo) firstNotification.getTag());
167                 removeViewFromIconRow(firstNotification);
168             }
169         });
170         animation.play(moveAndScaleIcon);
171 
172         // Shift all notifications (not the overflow) over to fill the gap.
173         int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
174         if (mRtl) {
175             gapWidth = -gapWidth;
176         }
177         if (!mOverflowNotifications.isEmpty()) {
178             NotificationInfo notification = mOverflowNotifications.remove(0);
179             mNotifications.add(notification);
180             View iconFromOverflow = addNotificationIconForInfo(notification);
181             animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
182         }
183         int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
184         // We have to reset the translation X to 0 when the new main notification
185         // is removed from the footer.
186         PropertyResetListener<View, Float> propertyResetListener
187                 = new PropertyResetListener<>(TRANSLATION_X, 0f);
188         for (int i = 0; i < numIcons; i++) {
189             final View child = mIconRow.getChildAt(i);
190             Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
191             shiftChild.addListener(propertyResetListener);
192             animation.play(shiftChild);
193         }
194         animation.start();
195     }
196 
removeViewFromIconRow(View child)197     private void removeViewFromIconRow(View child) {
198         mIconRow.removeView(child);
199         mNotifications.remove(child.getTag());
200         updateOverflowEllipsisVisibility();
201         if (mIconRow.getChildCount() == 0) {
202             // There are no more icons in the footer, so hide it.
203             if (mContainer != null) {
204                 mContainer.removeFooter();
205             }
206         }
207     }
208 
trimNotifications(List<String> notifications)209     public void trimNotifications(List<String> notifications) {
210         if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
211             return;
212         }
213         Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
214         while (overflowIterator.hasNext()) {
215             if (!notifications.contains(overflowIterator.next().notificationKey)) {
216                 overflowIterator.remove();
217             }
218         }
219         for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
220             View child = mIconRow.getChildAt(i);
221             NotificationInfo childInfo = (NotificationInfo) child.getTag();
222             if (!notifications.contains(childInfo.notificationKey)) {
223                 removeViewFromIconRow(child);
224             }
225         }
226     }
227 }
228