2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.notification;
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;
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;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.List;
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.
49 public class NotificationFooterLayout extends LinearLayout {
51 public interface IconAnimationEndListener {
52 void onIconAnimationEnd(NotificationInfo animatedNotification);
55 private static final int MAX_FOOTER_NOTIFICATIONS = 5;
57 private static final Rect sTempRect = new Rect();
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<>();
63 LinearLayout.LayoutParams mIconLayoutParams;
64 private LinearLayout mIconRow;
65 private int mTextColor;
67 public NotificationFooterLayout(Context context) {
68 this(context, null, 0);
71 public NotificationFooterLayout(Context context, AttributeSet attrs) {
72 this(context, attrs, 0);
75 public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
76 super(context, attrs, defStyle);
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;
88 protected void onFinishInflate() {
89 super.onFinishInflate();
90 mIconRow = (LinearLayout) findViewById(R.id.icon_row);
93 public void applyColors(IconPalette iconPalette) {
94 setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
95 findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
96 mTextColor = iconPalette.textColor;
100 * Keep track of the NotificationInfo, and then update the UI when
101 * {@link #commitNotificationInfos()} is called.
103 public void addNotificationInfo(final NotificationInfo notificationInfo) {
104 if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
105 mNotifications.add(notificationInfo);
107 mOverflowNotifications.add(notificationInfo);
112 * Adds icons and potentially overflow text for all of the NotificationInfo's
113 * added using {@link #addNotificationInfo(NotificationInfo)}.
115 public void commitNotificationInfos() {
116 mIconRow.removeAllViews();
117 mViewsToInfos.clear();
119 for (int i = 0; i < mNotifications.size(); i++) {
120 NotificationInfo info = mNotifications.get(i);
121 addNotificationIconForInfo(info, false /* fromOverflow */);
124 if (!mOverflowNotifications.isEmpty()) {
125 TextView overflowText = new TextView(getContext());
126 overflowText.setTextColor(mTextColor);
127 updateOverflowText(overflowText);
128 mIconRow.addView(overflowText, mIconLayoutParams);
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();
138 // Add the notification before the overflow view.
141 icon.animate().alpha(1);
143 mIconRow.addView(icon, addIndex, mIconLayoutParams);
144 mViewsToInfos.put(icon, info);
147 private void updateOverflowText(TextView overflowTextView) {
148 overflowTextView.setText(getResources().getString(R.string.deep_notifications_overflow,
149 mOverflowNotifications.size()));
152 public void animateFirstNotificationTo(Rect toBounds,
153 final IconAnimationEndListener callback) {
154 AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
155 final View firstNotification = mIconRow.getChildAt(0);
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() {
166 public void onAnimationEnd(Animator animation) {
167 callback.onIconAnimationEnd(mViewsToInfos.get(firstNotification));
170 animation.play(moveAndScaleIcon);
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() {
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);
188 animation.play(shiftChild);
193 public void trimNotifications(List<String> notifications) {
194 if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
197 Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
198 while (overflowIterator.hasNext()) {
199 if (!notifications.contains(overflowIterator.next().notificationKey)) {
200 overflowIterator.remove();
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;
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 */);
222 if (overflowView != null) {
223 if (mOverflowNotifications.isEmpty()) {
224 mIconRow.removeView(overflowView);
226 updateOverflowText(overflowView);
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();