OSDN Git Service

Store notification keys in a List instead of a Set.
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / notification / NotificationFooterLayout.java
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.content.Context;
23 import android.content.res.ColorStateList;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.view.Gravity;
27 import android.view.View;
28 import android.widget.LinearLayout;
29 import android.widget.TextView;
30
31 import com.android.launcher3.Launcher;
32 import com.android.launcher3.LauncherAnimUtils;
33 import com.android.launcher3.LauncherViewPropertyAnimator;
34 import com.android.launcher3.R;
35 import com.android.launcher3.graphics.IconPalette;
36 import com.android.launcher3.popup.PopupContainerWithArrow;
37
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
43
44 /**
45  * A {@link LinearLayout} that contains icons of notifications. If there is only one icon,
46  * we also supply the notification text/secondary text like we do for the main notification.
47  * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "+x" overflow.
48  */
49 public class NotificationFooterLayout extends LinearLayout {
50
51     public interface IconAnimationEndListener {
52         void onIconAnimationEnd(NotificationInfo animatedNotification);
53     }
54
55     private static final int MAX_FOOTER_NOTIFICATIONS = 5;
56
57     private static final Rect sTempRect = new Rect();
58
59     private final List<NotificationInfo> mNotifications = new ArrayList<>();
60     private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
61     private final Map<View, NotificationInfo> mViewsToInfos = new HashMap<>();
62
63     LinearLayout.LayoutParams mIconLayoutParams;
64     private LinearLayout mIconRow;
65     private int mTextColor;
66
67     public NotificationFooterLayout(Context context) {
68         this(context, null, 0);
69     }
70
71     public NotificationFooterLayout(Context context, AttributeSet attrs) {
72         this(context, attrs, 0);
73     }
74
75     public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
76         super(context, attrs, defStyle);
77
78         int size = getResources().getDimensionPixelSize(
79                 R.dimen.notification_footer_icon_size);
80         int padding = getResources().getDimensionPixelSize(
81                 R.dimen.deep_shortcut_drawable_padding);
82         mIconLayoutParams = new LayoutParams(size, size);
83         mIconLayoutParams.setMarginStart(padding);
84         mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
85     }
86
87     @Override
88     protected void onFinishInflate() {
89         super.onFinishInflate();
90         mIconRow = (LinearLayout) findViewById(R.id.icon_row);
91     }
92
93     public void applyColors(IconPalette iconPalette) {
94         setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
95         findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
96         mTextColor = iconPalette.textColor;
97     }
98
99     /**
100      * Keep track of the NotificationInfo, and then update the UI when
101      * {@link #commitNotificationInfos()} is called.
102      */
103     public void addNotificationInfo(final NotificationInfo notificationInfo) {
104         if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
105             mNotifications.add(notificationInfo);
106         } else {
107             mOverflowNotifications.add(notificationInfo);
108         }
109     }
110
111     /**
112      * Adds icons and potentially overflow text for all of the NotificationInfo's
113      * added using {@link #addNotificationInfo(NotificationInfo)}.
114      */
115     public void commitNotificationInfos() {
116         mIconRow.removeAllViews();
117         mViewsToInfos.clear();
118
119         for (int i = 0; i < mNotifications.size(); i++) {
120             NotificationInfo info = mNotifications.get(i);
121             addNotificationIconForInfo(info, false /* fromOverflow */);
122         }
123
124         if (!mOverflowNotifications.isEmpty()) {
125             TextView overflowText = new TextView(getContext());
126             overflowText.setTextColor(mTextColor);
127             updateOverflowText(overflowText);
128             mIconRow.addView(overflowText, mIconLayoutParams);
129         }
130     }
131
132     private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
133         View icon = new View(getContext());
134         icon.setBackground(info.iconDrawable);
135         icon.setOnClickListener(info);
136         int addIndex = mIconRow.getChildCount();
137         if (fromOverflow) {
138             // Add the notification before the overflow view.
139             addIndex--;
140             icon.setAlpha(0);
141             icon.animate().alpha(1);
142         }
143         mIconRow.addView(icon, addIndex, mIconLayoutParams);
144         mViewsToInfos.put(icon, info);
145     }
146
147     private void updateOverflowText(TextView overflowTextView) {
148         overflowTextView.setText(getResources().getString(R.string.deep_notifications_overflow,
149                 mOverflowNotifications.size()));
150     }
151
152     public void animateFirstNotificationTo(Rect toBounds,
153             final IconAnimationEndListener callback) {
154         AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
155         final View firstNotification = mIconRow.getChildAt(0);
156
157         Rect fromBounds = sTempRect;
158         firstNotification.getGlobalVisibleRect(fromBounds);
159         float scale = (float) toBounds.height() / fromBounds.height();
160         Animator moveAndScaleIcon = new LauncherViewPropertyAnimator(firstNotification)
161                 .translationY(toBounds.top - fromBounds.top
162                         + (fromBounds.height() * scale - fromBounds.height()) / 2)
163                 .scaleX(scale).scaleY(scale);
164         moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
165             @Override
166             public void onAnimationEnd(Animator animation) {
167                 callback.onIconAnimationEnd(mViewsToInfos.get(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         int numIcons = mIconRow.getChildCount()
175                 - (mOverflowNotifications.isEmpty() ? 0 : 1);
176         for (int i = 1; i < numIcons; i++) {
177             final View child = mIconRow.getChildAt(i);
178             Animator shiftChild = new LauncherViewPropertyAnimator(child).translationX(-gapWidth);
179             shiftChild.addListener(new AnimatorListenerAdapter() {
180                 @Override
181                 public void onAnimationEnd(Animator animation) {
182                     // We have to set the translation X to 0 when the new main notification
183                     // is removed from the footer.
184                     // TODO: remove it here instead of expecting trimNotifications to do so.
185                     child.setTranslationX(0);
186                 }
187             });
188             animation.play(shiftChild);
189         }
190         animation.start();
191     }
192
193     public void trimNotifications(List<String> notifications) {
194         if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
195             return;
196         }
197         Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
198         while (overflowIterator.hasNext()) {
199             if (!notifications.contains(overflowIterator.next().notificationKey)) {
200                 overflowIterator.remove();
201             }
202         }
203         TextView overflowView = null;
204         for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
205             View child = mIconRow.getChildAt(i);
206             if (child instanceof TextView) {
207                 overflowView = (TextView) child;
208             } else {
209                 NotificationInfo childInfo = mViewsToInfos.get(child);
210                 if (!notifications.contains(childInfo.notificationKey)) {
211                     mIconRow.removeView(child);
212                     mNotifications.remove(childInfo);
213                     mViewsToInfos.remove(child);
214                     if (!mOverflowNotifications.isEmpty()) {
215                         NotificationInfo notification = mOverflowNotifications.remove(0);
216                         mNotifications.add(notification);
217                         addNotificationIconForInfo(notification, true /* fromOverflow */);
218                     }
219                 }
220             }
221         }
222         if (overflowView != null) {
223             if (mOverflowNotifications.isEmpty()) {
224                 mIconRow.removeView(overflowView);
225             } else {
226                 updateOverflowText(overflowView);
227             }
228         }
229         if (mIconRow.getChildCount() == 0) {
230             // There are no more icons in the secondary view, so hide it.
231             PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
232                     Launcher.getLauncher(getContext()));
233             int newHeight = getResources().getDimensionPixelSize(
234                     R.dimen.notification_footer_collapsed_height);
235             AnimatorSet collapseSecondary = LauncherAnimUtils.createAnimatorSet();
236             collapseSecondary.play(popup.animateTranslationYBy(getHeight() - newHeight,
237                     getResources().getInteger(R.integer.config_removeNotificationViewDuration)));
238             collapseSecondary.play(LauncherAnimUtils.animateViewHeight(
239                     this, getHeight(), newHeight));
240             collapseSecondary.start();
241         }
242     }
243 }