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