Introduced the Notification shelf
Started the implementation of a new notification
scrolling model that dynamically transforms notifications
into a new icon shelf.
Test: Add notifications, scroll!
Bug: 32437839
Change-Id: Ic28cc1c82f2cda2ffa7a312648903b5bc2408820
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index c5b4e84..6732e6c 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -19,13 +19,7 @@
android:id="@+id/notification_icon_area_inner"
android:layout_width="match_parent"
android:layout_height="match_parent" >
- <com.android.systemui.statusbar.StatusBarIconView
- android:id="@+id/moreIcon"
- android:layout_width="@dimen/status_bar_icon_size"
- android:layout_height="match_parent"
- android:src="@drawable/stat_notify_more"
- android:visibility="gone" />
- <com.android.systemui.statusbar.phone.IconMerger
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
android:id="@+id/notificationIcons"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
deleted file mode 100644
index 7df6bc6..0000000
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Extends FrameLayout -->
-<com.android.systemui.statusbar.NotificationOverflowContainer
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_summary_height"
- android:focusable="true"
- android:clickable="true"
- >
-
- <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
- <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <TextView
- android:id="@+id/more_text"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="12dp"
- android:layout_gravity="center_vertical"
- android:background="@drawable/keyguard_overflow_number_background"
- android:gravity="center"
- android:textColor="#ff686868"
- android:textStyle="bold"
- android:textSize="14dp"
- />
- <com.android.systemui.statusbar.StatusBarIconView
- android:id="@+id/more_icon_overflow"
- android:layout_width="@dimen/status_bar_icon_size"
- android:layout_height="match_parent"
- android:src="@drawable/stat_notify_more"
- android:tint="@color/keyguard_overflow_content_color"
- android:visibility="gone"
- />
- <com.android.systemui.statusbar.NotificationOverflowIconsView
- android:id="@+id/overflow_icons_view"
- android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
-
- <com.android.systemui.statusbar.notification.FakeShadowView
- android:id="@+id/fake_shadow"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
-</com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
new file mode 100644
index 0000000..fde61eb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -0,0 +1,47 @@
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Extends FrameLayout -->
+<com.android.systemui.statusbar.NotificationShelf
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_shelf_height"
+ android:focusable="true"
+ android:clickable="true"
+ >
+
+ <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+ <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="13dp"
+ android:layout_marginEnd="13dp"
+ android:gravity="center_vertical" />
+
+ <com.android.systemui.statusbar.notification.FakeShadowView
+ android:id="@+id/fake_shadow"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</com.android.systemui.statusbar.NotificationShelf>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 12f7881..943c67c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,8 +81,12 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height">148dp</dimen>
- <!-- Height of a the summary ("more card") notification on keyguard. -->
- <dimen name="notification_summary_height">44dp</dimen>
+ <!-- Height of a the shelf with the notification icons -->
+ <dimen name="notification_shelf_height">32dp</dimen>
+
+ <!-- The padding of a notification icon on top to the start of the notification. Used for custom
+ views where the distance can't be measured -->
+ <dimen name="notification_icon_appear_padding">15dp</dimen>
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
@@ -94,7 +98,7 @@
<dimen name="notification_gear_padding">20dp</dimen>
<!-- size at which Notification icons will be drawn in the status bar -->
- <dimen name="status_bar_icon_drawing_size">17dip</dimen>
+ <dimen name="status_bar_icon_drawing_size">17dp</dimen>
<!-- opacity at which Notification icons will be drawn in the status bar -->
<item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index bc46548..0616db5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -41,7 +41,7 @@
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
- * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
+ * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
* to implement dimming/activating on Keyguard for the double-tap gesture
*/
public abstract class ActivatableNotificationView extends ExpandableOutlineView {
@@ -131,7 +131,7 @@
private final int mLegacyColor;
private final int mNormalColor;
private final int mLowPriorityColor;
- private boolean mIsBelowSpeedBump;
+ private boolean mIsBelowShelf;
private FalsingManager mFalsingManager;
private boolean mTrackTouch;
@@ -443,10 +443,10 @@
}
@Override
- public void setBelowSpeedBump(boolean below) {
- super.setBelowSpeedBump(below);
- if (below != mIsBelowSpeedBump) {
- mIsBelowSpeedBump = below;
+ public void setBelowShelf(boolean below) {
+ super.setBelowShelf(below);
+ if (below != mIsBelowShelf) {
+ mIsBelowShelf = below;
updateBackgroundTint();
}
}
@@ -849,7 +849,7 @@
return mBgTint;
} else if (mShowingLegacyBackground) {
return mLegacyColor;
- } else if (mIsBelowSpeedBump) {
+ } else if (mIsBelowShelf) {
return mLowPriorityColor;
} else {
return mNormalColor;
@@ -861,7 +861,7 @@
return mTintedRippleColor;
} else if (mShowingLegacyBackground) {
return mTintedRippleColor;
- } else if (mIsBelowSpeedBump) {
+ } else if (mIsBelowShelf) {
return mLowPriorityRippleColor;
} else {
return mNormalRippleColor;
@@ -907,7 +907,7 @@
setTintColor(0);
resetBackgroundAlpha();
setShowingLegacyBackground(false);
- setBelowSpeedBump(false);
+ setBelowShelf(false);
}
public boolean hasSameBgColor(ActivatableNotificationView otherView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 19e511cf..4649d50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -42,7 +42,6 @@
import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
@@ -258,7 +257,7 @@
protected boolean mShowLockscreenNotifications;
protected boolean mAllowLockscreenRemoteInput;
- protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
+ protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
@@ -1025,9 +1024,7 @@
}
}
- if (entry.icon != null) {
- entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- }
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
}
public boolean isMediaNotification(NotificationData.Entry entry) {
@@ -2160,13 +2157,14 @@
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
- final StatusBarIconView iconView = createIcon(sbn);
- if (iconView == null) {
- return null;
+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ try {
+ entry.createIcons(mContext, sbn);
+ } catch (NotificationData.IconException exception) {
+ handleNotificationError(sbn, exception.getMessage());
}
// Construct the expanded view.
- NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
if (!inflateViews(entry, mStackScroller)) {
handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
return null;
@@ -2174,33 +2172,6 @@
return entry;
}
- public StatusBarIconView createIcon(StatusBarNotification sbn) {
- // Construct the icon.
- Notification n = sbn.getNotification();
- final StatusBarIconView iconView = new StatusBarIconView(mContext,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
- iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
- final Icon smallIcon = n.getSmallIcon();
- if (smallIcon == null) {
- handleNotificationError(sbn,
- "No small icon in notification from " + sbn.getPackageName());
- return null;
- }
- final StatusBarIcon ic = new StatusBarIcon(
- sbn.getUser(),
- sbn.getPackageName(),
- smallIcon,
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(mContext, n));
- if (!iconView.set(ic)) {
- handleNotificationError(sbn, "Couldn't create icon: " + ic);
- return null;
- }
- return iconView;
- }
-
protected void addNotificationViews(Entry entry, RankingMap ranking) {
if (entry == null) {
return;
@@ -2220,8 +2191,6 @@
* Updates expanded, dimmed and locked states of notification rows.
*/
protected void updateRowStates() {
- mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
-
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
final int N = activeNotifications.size();
@@ -2252,9 +2221,6 @@
|| (onKeyguard && !childWithVisibleSummary
&& (visibleNotifications >= maxNotifications || !showOnKeyguard))) {
entry.row.setVisibility(View.GONE);
- if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) {
- mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
- }
} else {
boolean wasGone = entry.row.getVisibility() == View.GONE;
entry.row.setVisibility(View.VISIBLE);
@@ -2269,12 +2235,9 @@
}
}
- mStackScroller.updateOverflowContainerVisibility(onKeyguard
- && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0);
-
mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
- mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
+ mStackScroller.changeViewPosition(mNotificationShelf,
mStackScroller.getChildCount() - 3);
}
@@ -2367,48 +2330,28 @@
mGroupManager.onEntryUpdated(entry, oldNotification);
boolean updateSuccessful = false;
- if (applyInPlace) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- try {
- if (entry.icon != null) {
- // Update the icon
- final StatusBarIcon ic = new StatusBarIcon(
- notification.getUser(),
- notification.getPackageName(),
- n.getSmallIcon(),
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(mContext, n));
- entry.icon.setNotification(n);
- if (!entry.icon.set(ic)) {
- handleNotificationError(notification, "Couldn't update icon: " + ic);
- return;
- }
+ try {
+ if (applyInPlace) {
+ if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+ try {
+ entry.updateIcons(mContext, n);
+ updateNotificationViews(entry, notification);
+ updateSuccessful = true;
+ } catch (RuntimeException e) {
+ // It failed to apply cleanly.
+ Log.w(TAG, "Couldn't reapply views for package " +
+ notification.getPackageName(), e);
}
- updateNotificationViews(entry, notification);
- updateSuccessful = true;
}
- catch (RuntimeException e) {
- // It failed to apply cleanly.
- Log.w(TAG, "Couldn't reapply views for package " +
- notification.getPackageName(), e);
+ if (!updateSuccessful) {
+ entry.updateIcons(mContext, n);
+ if (!inflateViews(entry, mStackScroller)) {
+ handleNotificationError(notification, "Couldn't update remote views for: "
+ + notification);
+ }
}
- }
- if (!updateSuccessful) {
- if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
- final StatusBarIcon ic = new StatusBarIcon(
- notification.getUser(),
- notification.getPackageName(),
- n.getSmallIcon(),
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(mContext, n));
- entry.icon.setNotification(n);
- entry.icon.set(ic);
- if (!inflateViews(entry, mStackScroller)) {
- handleNotificationError(notification, "Couldn't update remote views for: "
- + notification);
- }
+ } catch (NotificationData.IconException e) {
+ handleNotificationError(notification, e.getMessage());
}
updateHeadsUp(key, entry, shouldPeek, alertAgain);
updateNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c9a5fdf..6e690f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -318,6 +318,10 @@
return mStatusBarNotification;
}
+ public NotificationData.Entry getEntry() {
+ return mEntry;
+ }
+
public boolean isHeadsUp() {
return mIsHeadsUp;
}
@@ -785,6 +789,26 @@
mVetoButton.setOnClickListener(listener);
}
+ public View getNotificationIcon() {
+ NotificationHeaderView notificationHeader = getNotificationHeader();
+ if (notificationHeader != null) {
+ return notificationHeader.getIcon();
+ }
+ return null;
+ }
+
+ /**
+ * @return whether the notification is currently showing a view with an icon.
+ */
+ public boolean isShowingIcon() {
+ if (mIsSummaryWithChildren) {
+ return true;
+ }
+ NotificationContentView showingLayout = getShowingLayout();
+ NotificationHeaderView notificationHeader = showingLayout.getVisibleNotificationHeader();
+ return notificationHeader != null;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index b97d345..29a0e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -263,7 +263,11 @@
public abstract void performAddAnimation(long delay, long duration);
- public void setBelowSpeedBump(boolean below) {
+ /**
+ * Set the notification appearance to be below the shelf.
+ * @param below true if it is below.
+ */
+ public void setBelowShelf(boolean below) {
}
/**
@@ -445,6 +449,15 @@
}
/**
+ * @return whether the current view doesn't add height to the overall content. This means that
+ * if it is added to a list of items, it's content will still have the same height.
+ * An example is the notification shelf, that is always placed on top of another view.
+ */
+ public boolean hasNoContentHeight() {
+ return false;
+ }
+
+ /**
* A listener notifying when {@link #getActualHeight} changes.
*/
public interface OnHeightChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 7019880..3687f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -19,6 +19,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
+import android.graphics.drawable.Icon;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -26,8 +27,11 @@
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.view.View;
+import android.widget.ImageView;
import android.widget.RemoteViews;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -48,9 +52,11 @@
public static final class Entry {
private static final long LAUNCH_COOLDOWN = 2000;
private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
+ private static final int COLOR_INVALID = 1;
public String key;
public StatusBarNotification notification;
public StatusBarIconView icon;
+ public StatusBarIconView expandedIcon;
public ExpandableNotificationRow row; // the outer expanded view
private boolean interruption;
public boolean autoRedacted; // whether the redacted notification was generated by us
@@ -62,11 +68,12 @@
public RemoteViews cachedHeadsUpContentView;
public RemoteViews cachedPublicContentView;
public CharSequence remoteInputText;
+ private int mCachedContrastColor = COLOR_INVALID;
+ private int mCachedContrastColorIsFor = COLOR_INVALID;
- public Entry(StatusBarNotification n, StatusBarIconView ic) {
+ public Entry(StatusBarNotification n) {
this.key = n.getKey();
this.notification = n;
- this.icon = ic;
}
public void setInterruption() {
@@ -165,6 +172,85 @@
public boolean hasJustLaunchedFullScreenIntent() {
return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
}
+
+ /**
+ * Create the icons for a notification
+ * @param context the context to create the icons with
+ * @param sbn the notification
+ * @throws IconException
+ */
+ public void createIcons(Context context, StatusBarNotification sbn) throws IconException {
+ Notification n = sbn.getNotification();
+ final Icon smallIcon = n.getSmallIcon();
+ if (smallIcon == null) {
+ throw new IconException("No small icon in notification from "
+ + sbn.getPackageName());
+ }
+
+ // Construct the icon.
+ icon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+ icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ // Construct the expanded icon.
+ expandedIcon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+ expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ final StatusBarIcon ic = new StatusBarIcon(
+ sbn.getUser(),
+ sbn.getPackageName(),
+ smallIcon,
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ icon = null;
+ expandedIcon = null;
+ throw new IconException("Couldn't create icon: " + ic);
+ }
+ }
+
+ public void setIconTag(int key, Object tag) {
+ if (icon != null) {
+ icon.setTag(key, tag);
+ expandedIcon.setTag(key, tag);
+ }
+ }
+
+ /**
+ * Update the notification icons.
+ * @param context the context to create the icons with.
+ * @param n the notification to read the icon from.
+ * @throws IconException
+ */
+ public void updateIcons(Context context, Notification n) throws IconException {
+ if (icon != null) {
+ // Update the icon
+ final StatusBarIcon ic = new StatusBarIcon(
+ notification.getUser(),
+ notification.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ icon.setNotification(n);
+ expandedIcon.setNotification(n);
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ throw new IconException("Couldn't update icon: " + ic);
+ }
+ }
+ }
+
+ public int getContrastedColor(Context context) {
+ int rawColor = notification.getNotification().color;
+ if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
+ return mCachedContrastColor;
+ }
+ final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor);
+ mCachedContrastColorIsFor = rawColor;
+ mCachedContrastColor = contrasted;
+ return mCachedContrastColor;
+ }
}
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
@@ -472,4 +558,10 @@
public String getCurrentMediaNotificationKey();
public NotificationGroupManager getGroupManager();
}
+
+ public static class IconException extends Exception {
+ IconException(String error) {
+ super(error);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
deleted file mode 100644
index 8e8ce1a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-/**
- * Container view for overflowing notification icons on Keyguard.
- */
-public class NotificationOverflowContainer extends ActivatableNotificationView {
-
- private NotificationOverflowIconsView mIconsView;
- private ViewInvertHelper mViewInvertHelper;
- private boolean mDark;
- private View mContent;
-
- public NotificationOverflowContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
- mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
- mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow));
- mContent = findViewById(R.id.content);
- mViewInvertHelper = new ViewInvertHelper(mContent,
- NotificationPanelView.DOZE_ANIMATION_DURATION);
- }
-
- @Override
- public void setDark(boolean dark, boolean fade, long delay) {
- super.setDark(dark, fade, delay);
- if (mDark == dark) return;
- mDark = dark;
- if (fade) {
- mViewInvertHelper.fade(dark, delay);
- } else {
- mViewInvertHelper.update(dark);
- }
- }
-
- @Override
- protected View getContentView() {
- return mContent;
- }
-
- public NotificationOverflowIconsView getIconsView() {
- return mIconsView;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
deleted file mode 100644
index 88bb714..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.app.Notification;
-import android.content.Context;
-import android.graphics.PorterDuff;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.IconMerger;
-
-/**
- * A view to display all the overflowing icons on Keyguard.
- */
-public class NotificationOverflowIconsView extends IconMerger {
-
- private TextView mMoreText;
- private int mTintColor;
- private int mIconSize;
- private NotificationColorUtil mNotificationColorUtil;
-
- public NotificationOverflowIconsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mNotificationColorUtil = NotificationColorUtil.getInstance(getContext());
- mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color);
- mIconSize = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_icon_size);
- }
-
- public void setMoreText(TextView moreText) {
- mMoreText = moreText;
- }
-
- public void addNotification(NotificationData.Entry notification) {
- StatusBarIconView v = new StatusBarIconView(getContext(), "",
- notification.notification.getNotification());
- v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- addView(v, mIconSize, mIconSize);
- v.set(notification.icon.getStatusBarIcon());
- applyColor(notification.notification.getNotification(), v);
- updateMoreText();
- }
-
- private void applyColor(Notification notification, StatusBarIconView view) {
- view.setColorFilter(mTintColor, PorterDuff.Mode.MULTIPLY);
- }
-
- private void updateMoreText() {
- mMoreText.setText(
- getResources().getString(R.string.keyguard_more_overflow_text, getChildCount()));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
new file mode 100644
index 0000000..28b9099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.stack.AmbientState;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.stack.StackScrollState;
+import com.android.systemui.statusbar.stack.ViewState;
+
+import java.util.ArrayList;
+import java.util.WeakHashMap;
+
+/**
+ * A notification shelf view that is placed inside the notification scroller. It manages the
+ * overflow icons that don't fit into the regular list anymore.
+ */
+public class NotificationShelf extends ActivatableNotificationView {
+
+ private ViewInvertHelper mViewInvertHelper;
+ private boolean mDark;
+ private NotificationIconContainer mNotificationIconContainer;
+ private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
+ private ShelfState mShelfState;
+ private int[] mTmp = new int[2];
+ private boolean mHideBackground;
+ private int mIconAppearTopPadding;
+
+ public NotificationShelf(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mNotificationIconContainer = (NotificationIconContainer) findViewById(R.id.content);
+ mNotificationIconContainer.setClipChildren(false);
+ mNotificationIconContainer.setClipToPadding(false);
+ setClipToActualHeight(false);
+ setClipChildren(false);
+ setClipToPadding(false);
+ mNotificationIconContainer.setShowAllIcons(false);
+ mViewInvertHelper = new ViewInvertHelper(mNotificationIconContainer,
+ NotificationPanelView.DOZE_ANIMATION_DURATION);
+ mShelfState = new ShelfState();
+ mShelfState.iconStates = mNotificationIconContainer.getIconStates();
+ initDimens();
+ }
+
+ private void initDimens() {
+ mIconAppearTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_icon_appear_padding);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ initDimens();
+ }
+
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+ super.setDark(dark, fade, delay);
+ if (mDark == dark) return;
+ mDark = dark;
+ if (fade) {
+ mViewInvertHelper.fade(dark, delay);
+ } else {
+ mViewInvertHelper.update(dark);
+ }
+ }
+
+ @Override
+ protected View getContentView() {
+ return mNotificationIconContainer;
+ }
+
+ public NotificationIconContainer getNotificationIconContainer() {
+ return mNotificationIconContainer;
+ }
+
+ @Override
+ public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+ return mShelfState;
+ }
+
+ public void updateState(StackScrollState resultState,
+ StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
+ int shelfIndex = ambientState.getShelfIndex();
+ shelfIndex = shelfIndex == -1
+ ? algorithmState.visibleChildren.size() - 1
+ : shelfIndex - 1;
+ if (shelfIndex != -1) {
+ float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ + ambientState.getStackTranslation();
+ ExpandableView lastView = algorithmState.visibleChildren.get(shelfIndex);
+ ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
+ float viewEnd = lastViewState.yTranslation + lastViewState.height;
+ mShelfState.copyFrom(lastViewState);
+ mShelfState.height = getIntrinsicHeight();
+ mShelfState.yTranslation = Math.min(viewEnd, maxShelfEnd) - mShelfState.height;
+ mShelfState.zTranslation = Math.max(mShelfState.zTranslation,
+ ambientState.getBaseZHeight());
+ mShelfState.clipTopAmount = 0;
+ mShelfState.alpha = 1.0f;
+ mShelfState.belowShelf = false;
+ mShelfState.shadowAlpha = 1.0f;
+ mShelfState.isBottomClipped = false;
+ mShelfState.hideSensitive = false;
+
+ mShelfState.resetState();
+ float numIconsInShelf = 0.0f;
+ float viewStart;
+ float maxShelfStart = maxShelfEnd - mShelfState.height;
+ // find the first view that doesn't overlap with the shelf
+ for (int i = shelfIndex; i >= 0; i--) {
+ lastView = algorithmState.visibleChildren.get(i);
+ lastViewState = resultState.getViewStateForView(lastView);
+ ExpandableNotificationRow row = null;
+ if (lastView instanceof ExpandableNotificationRow) {
+ row = (ExpandableNotificationRow) lastView;
+ }
+ viewStart = lastViewState.yTranslation;
+ viewEnd = viewStart + lastView.getIntrinsicHeight();
+ if (viewEnd > maxShelfStart) {
+ if (viewStart < maxShelfStart) {
+ float transitionAmount = 1.0f - ((maxShelfStart - viewStart) /
+ lastView.getIntrinsicHeight());
+ numIconsInShelf += transitionAmount;
+ } else {
+ numIconsInShelf += 1.0f;
+ lastViewState.hidden = true;
+ }
+ }
+ if (row != null){
+ // Not in the shelf yet, Icon needs to be placed on top of the notification icon
+ updateIconAppearance(row.getEntry(), lastViewState, mShelfState);
+ }
+ }
+ mShelfState.iconStates = mNotificationIconContainer.calculateIconStates(
+ numIconsInShelf);
+ mShelfState.hidden = numIconsInShelf == 0.0f;
+ mShelfState.hideBackground = numIconsInShelf < 1.0f;
+ } else {
+ mShelfState.hideBackground = true;
+ mShelfState.hidden = true;
+ mShelfState.location = ExpandableViewState.LOCATION_GONE;
+ }
+ }
+
+ private void updateIconAppearance(NotificationData.Entry entry, ExpandableViewState rowState,
+ ShelfState shelfState) {
+ StatusBarIconView icon = entry.expandedIcon;
+ ViewState iconState = shelfState.iconStates.get(icon);
+ View rowIcon = entry.row.getNotificationIcon();
+ float notificationIconPosition = rowState.yTranslation;
+ float notificationIconSize = 0.0f;
+ int iconTopPadding;
+ if (rowIcon != null) {
+ iconTopPadding = getIconTopPadding(rowIcon);
+ notificationIconSize = rowIcon.getHeight();
+ } else {
+ iconTopPadding = mIconAppearTopPadding;
+ }
+ notificationIconPosition += iconTopPadding;
+ float shelfIconPosition = mShelfState.yTranslation + icon.getTop();
+ shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
+ float transitionDistance = getIntrinsicHeight() * 1.5f;
+ float transformationStartPosition = mShelfState.yTranslation - transitionDistance;
+ float transitionAmount = 0.0f;
+ if (rowState.yTranslation < transformationStartPosition) {
+ // We simply place it on the icon of the notification
+ iconState.yTranslation = notificationIconPosition - shelfIconPosition;
+ } else {
+ transitionAmount = (rowState.yTranslation - transformationStartPosition)
+ / transitionDistance;
+ float startPosition = transformationStartPosition + iconTopPadding;
+ iconState.yTranslation = NotificationUtils.interpolate(
+ startPosition - shelfIconPosition, 0, transitionAmount);
+ }
+ float shelfIconSize = icon.getHeight() * icon.getIconScale();
+ Float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
+ transitionAmount);
+ iconState.scaleX = newSize / icon.getHeight();
+ iconState.scaleY = iconState.scaleX;
+ iconState.hidden = transitionAmount == 0.0f;
+ if (!entry.row.isShowingIcon()) {
+ iconState.alpha = transitionAmount;
+ }
+ }
+
+ private int getIconTopPadding(View icon) {
+ View view = icon;
+ int topPadding = 0;
+ while (view.getParent() instanceof ViewGroup) {
+ topPadding += view.getTop();
+ view = (View) view.getParent();
+ if (view instanceof ExpandableNotificationRow) {
+ return topPadding;
+ }
+ }
+ return topPadding;
+ }
+
+ public int getNotificationMergeSize() {
+ return getIntrinsicHeight();
+ }
+
+ @Override
+ public boolean hasNoContentHeight() {
+ return true;
+ }
+
+ private void setHideBackground(boolean hideBackground) {
+ mHideBackground = hideBackground;
+ updateBackground();
+ }
+
+ @Override
+ protected boolean shouldHideBackground() {
+ return super.shouldHideBackground() || mHideBackground;
+ }
+
+ private class ShelfState extends ExpandableViewState {
+ private WeakHashMap<View, ViewState> iconStates = new WeakHashMap<>();
+ private boolean hideBackground;
+
+ @Override
+ public void applyToView(View view) {
+ super.applyToView(view);
+ mNotificationIconContainer.applyIconStates(iconStates);
+ setHideBackground(hideBackground);
+ }
+
+ public void resetState() {
+ mNotificationIconContainer.resetViewStates(iconStates);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index cdfdad4..03e3662 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -54,6 +54,7 @@
private Notification mNotification;
private final boolean mBlocked;
private int mDensity;
+ private float mIconScale = 1.0f;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -86,9 +87,13 @@
Resources res = mContext.getResources();
final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
- final float scale = (float)imageBounds / (float)outerBounds;
- setScaleX(scale);
- setScaleY(scale);
+ mIconScale = (float)imageBounds / (float)outerBounds;
+ setScaleX(mIconScale);
+ setScaleY(mIconScale);
+ }
+
+ public float getIconScale() {
+ return mIconScale;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
deleted file mode 100644
index f86badb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-public class IconMerger extends LinearLayout {
- private static final String TAG = "IconMerger";
- private static final boolean DEBUG = false;
-
- private int mIconSize;
- private int mIconHPadding;
-
- private View mMoreView;
-
- public IconMerger(Context context, AttributeSet attrs) {
- super(context, attrs);
- reloadDimens();
- if (DEBUG) {
- setBackgroundColor(0x800099FF);
- }
- }
-
- private void reloadDimens() {
- Resources res = mContext.getResources();
- mIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
- mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- reloadDimens();
- }
-
- public void setOverflowIndicator(View v) {
- mMoreView = v;
- }
-
- private int getFullIconWidth() {
- return mIconSize + 2 * mIconHPadding;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- // we need to constrain this to an integral multiple of our children
- int width = getMeasuredWidth();
- setMeasuredDimension(width - (width % getFullIconWidth()), getMeasuredHeight());
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- checkOverflow(r - l);
- }
-
- private void checkOverflow(int width) {
- if (mMoreView == null) return;
-
- final int N = getChildCount();
- int visibleChildren = 0;
- for (int i=0; i<N; i++) {
- if (getChildAt(i).getVisibility() != GONE) visibleChildren++;
- }
- final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE);
- // let's assume we have one more slot if the more icon is already showing
- if (overflowShown) visibleChildren --;
- final boolean moreRequired = visibleChildren * getFullIconWidth() > width;
- if (moreRequired != overflowShown) {
- post(new Runnable() {
- @Override
- public void run() {
- mMoreView.setVisibility(moreRequired ? View.VISIBLE : View.GONE);
- }
- });
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 784cb48..70beac8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -80,7 +80,7 @@
mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
mMoreCardNotificationAmount =
- (float) res.getDimensionPixelSize(R.dimen.notification_summary_height) /
+ (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) /
res.getDimensionPixelSize(R.dimen.notification_min_height);
mDensity = res.getDisplayMetrics().density;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index cbaab14..72e84a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,16 +8,20 @@
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.ImageView;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
+import java.util.function.Function;
/**
* A controller for the space in the status bar to the left of the system icons. This area is
@@ -32,13 +36,17 @@
private PhoneStatusBar mPhoneStatusBar;
protected View mNotificationIconArea;
- private IconMerger mNotificationIcons;
- private ImageView mMoreIcon;
+ private NotificationShelf mNotificationIconAreaScroller;
+ private NotificationIconContainer mNotificationIcons;
+ private NotificationIconContainer mNotificationIconsScroller;
private final Rect mTintArea = new Rect();
+ private NotificationStackScrollLayout mNotificationScrollLayout;
+ private Context mContext;
public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
mPhoneStatusBar = phoneStatusBar;
mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+ mContext = context;
initializeNotificationAreaViews(context);
}
@@ -55,15 +63,13 @@
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
+ mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
+ R.id.notificationIcons);
- mNotificationIcons =
- (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
+ mNotificationIconAreaScroller = mPhoneStatusBar.getNotificationShelf();
+ mNotificationIconsScroller = mNotificationIconAreaScroller.getNotificationIconContainer();
- mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
- if (mMoreIcon != null) {
- mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
- mNotificationIcons.setOverflowIndicator(mMoreIcon);
- }
+ mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
}
public void onDensityOrFontScaleChanged(Context context) {
@@ -114,9 +120,7 @@
*/
public void setIconTint(int iconTint) {
mIconTint = iconTint;
- if (mMoreIcon != null) {
- mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
- }
+ mNotificationIcons.setIconTint(mIconTint);
applyNotificationIconsTint();
}
@@ -144,24 +148,54 @@
* Updates the notifications with the given list of notifications to display.
*/
public void updateNotificationIcons(NotificationData notificationData) {
- final LinearLayout.LayoutParams params = generateIconLayoutParams();
- ArrayList<NotificationData.Entry> activeNotifications =
- notificationData.getActiveNotifications();
- final int size = activeNotifications.size();
- ArrayList<StatusBarIconView> toShow = new ArrayList<>(size);
+ updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
+ updateIconsForLayout(notificationData, entry -> entry.expandedIcon,
+ mNotificationIconsScroller);
+
+ applyNotificationIconsTint();
+ ArrayList<NotificationData.Entry> activeNotifications
+ = notificationData.getActiveNotifications();
+ for (int i = 0; i < activeNotifications.size(); i++) {
+ NotificationData.Entry entry = activeNotifications.get(i);
+ boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
+ boolean colorize = !isPreL
+ || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
+ if (colorize) {
+ int color = entry.getContrastedColor(mContext);
+ entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+ }
+ }
+ }
+
+ /**
+ * Updates the notification icons for a host layout. This will ensure that the notification
+ * host layout will have the same icons like the ones in here.
+ *
+ * @param notificationData the notification data to look up which notifications are relevant
+ * @param function A function to look up an icon view based on an entry
+ * @param hostLayout which layout should be updated
+ */
+ private void updateIconsForLayout(NotificationData notificationData,
+ Function<NotificationData.Entry, StatusBarIconView> function,
+ ViewGroup hostLayout) {
+ ArrayList<StatusBarIconView> toShow = new ArrayList<>(
+ mNotificationScrollLayout.getChildCount());
// Filter out ambient notifications and notification children.
- for (int i = 0; i < size; i++) {
- NotificationData.Entry ent = activeNotifications.get(i);
- if (shouldShowNotification(ent, notificationData)) {
- toShow.add(ent.icon);
+ for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
+ View view = mNotificationScrollLayout.getChildAt(i);
+ if (view instanceof ExpandableNotificationRow) {
+ NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
+ if (shouldShowNotification(ent, notificationData)) {
+ toShow.add(function.apply(ent));
+ }
}
}
ArrayList<View> toRemove = new ArrayList<>();
- for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
- View child = mNotificationIcons.getChildAt(i);
+ for (int i = 0; i < hostLayout.getChildCount(); i++) {
+ View child = hostLayout.getChildAt(i);
if (!toShow.contains(child)) {
toRemove.add(child);
}
@@ -169,29 +203,28 @@
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
- mNotificationIcons.removeView(toRemove.get(i));
+ hostLayout.removeView(toRemove.get(i));
}
+ final LinearLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
- mNotificationIcons.addView(v, i, params);
+ hostLayout.addView(v, i, params);
}
}
// Re-sort notification icons
- final int childCount = mNotificationIcons.getChildCount();
+ final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
- View actual = mNotificationIcons.getChildAt(i);
+ View actual = hostLayout.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
- mNotificationIcons.removeView(expected);
- mNotificationIcons.addView(expected, i);
+ hostLayout.removeView(expected);
+ hostLayout.addView(expected, i);
}
-
- applyNotificationIconsTint();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
new file mode 100644
index 0000000..bf7886d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
+import com.android.systemui.statusbar.stack.ViewState;
+
+import java.util.WeakHashMap;
+
+/**
+ * A container for notification icons. It handles overflowing icons properly and positions them
+ * correctly on the screen.
+ */
+public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+ private static final String TAG = "NotificationIconContainer";
+
+ private boolean mShowAllIcons = true;
+ private int mIconTint;
+ private WeakHashMap<View, ViewState> mIconStates = new WeakHashMap<>();
+
+ public NotificationIconContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ float centerY = getHeight() / 2.0f;
+ // we layout all our children on the left at the top
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ // We need to layout all children even the GONE ones, such that the heights are
+ // calculated correctly as they are used to calculate how many we can fit on the screen
+ int width = child.getMeasuredWidth();
+ int height = child.getMeasuredHeight();
+ int top = (int) (centerY - height / 2.0f);
+ child.layout(0, top, width, top + height);
+ }
+ if (mShowAllIcons) {
+ resetViewStates(mIconStates);
+ calculateIconStates(getChildCount());
+ applyIconStates(mIconStates);
+ }
+ }
+
+ public void applyIconStates(WeakHashMap<View, ViewState> iconStates) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ViewState childState = iconStates.get(child);
+ if (childState != null) {
+ childState.applyToView(child);
+ }
+ }
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ mIconStates.put(child, new ViewState());
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mIconStates.remove(child);
+ }
+
+ public void setIconTint(int iconTint) {
+ mIconTint = iconTint;
+ }
+
+ public void resetViewStates(WeakHashMap<View, ViewState> viewStates) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ ViewState iconState = mIconStates.get(view);
+ iconState.initFrom(view);
+ }
+ }
+
+ /**
+ * Gets a new state based on the number of visible icons starting from the right.
+ * If this is not a whole number, the fraction means by how much the icon is appearing.
+ */
+ public WeakHashMap<View, ViewState> calculateIconStates(float numberOfVisibleIcons) {
+ int childCount = getChildCount();
+ float visibleIconStart = childCount - numberOfVisibleIcons;
+ int firstIconIndex = (int) visibleIconStart;
+ float translationX = 0.0f;
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ ViewState iconState = mIconStates.get(view);
+ if (i >= firstIconIndex) {
+ iconState.xTranslation = translationX;
+ float appearAmount = 1.0f;
+ if (i == firstIconIndex) {
+ appearAmount = 1.0f - (visibleIconStart - firstIconIndex);
+ }
+ translationX += appearAmount * view.getWidth();
+ }
+ }
+ return mIconStates;
+ }
+
+ public WeakHashMap<View, ViewState> getIconStates() {
+ return mIconStates;
+ }
+
+ /**
+ * Sets whether the layout should always show all icons.
+ * If this is true, the icon positions will be updated on layout.
+ * If this if false, the layout is managed from the outside and layouting won't trigger a
+ * repositioning of the icons.
+ */
+ public void setShowAllIcons(boolean showAllIcons) {
+ mShowAllIcons = showAllIcons;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 068631d..dc30999 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -404,10 +404,11 @@
int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
R.dimen.notification_divider_height));
final int overflowheight = getResources().getDimensionPixelSize(
- R.dimen.notification_summary_height);
- float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+ R.dimen.notification_shelf_height);
+ float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
+ + notificationPadding;
float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
- - bottomStackSize;
+ - shelfSize;
int count = 0;
for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
@@ -429,6 +430,16 @@
availableSpace -= child.getMinHeight() + notificationPadding;
if (availableSpace >= 0 && count < maximum) {
count++;
+ } else if (availableSpace > -shelfSize) {
+ // if we are exactly the last view, then we can show us still!
+ for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+ if (mNotificationStackScroller.getChildAt(j)
+ instanceof ExpandableNotificationRow) {
+ return count;
+ }
+ }
+ count++;
+ return count;
} else {
return count;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9b93527..b847cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -164,7 +164,7 @@
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
@@ -659,10 +659,12 @@
array.clear();
}
- private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() {
+ private final View.OnClickListener mShelfClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- goToLockedShade(null);
+ if (mState == StatusBarState.KEYGUARD) {
+ goToLockedShade(null);
+ }
}
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
@@ -812,7 +814,7 @@
mStackScroller.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setOnGroupChangeListener(mStackScroller);
- inflateOverflowContainer();
+ inflateShelf();
inflateEmptyShadeView();
inflateDismissView();
mExpandedContents = mStackScroller;
@@ -1049,13 +1051,13 @@
return new BatteryControllerImpl(mContext);
}
- private void inflateOverflowContainer() {
- mKeyguardIconOverflowContainer =
- (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
- R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
- mKeyguardIconOverflowContainer.setOnActivatedListener(this);
- mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
- mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer);
+ private void inflateShelf() {
+ mNotificationShelf =
+ (NotificationShelf) LayoutInflater.from(mContext).inflate(
+ R.layout.status_bar_notification_icon_container, mStackScroller, false);
+ mNotificationShelf.setOnActivatedListener(this);
+ mNotificationShelf.setOnClickListener(mShelfClickListener);
+ mStackScroller.setShelf(mNotificationShelf);
}
@Override
@@ -1072,7 +1074,7 @@
updateClearAll();
inflateEmptyShadeView();
updateEmptyShadeView();
- inflateOverflowContainer();
+ inflateShelf();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
mUserInfoController.onDensityOrFontScaleChanged();
if (mUserSwitcherController != null) {
@@ -1866,7 +1868,7 @@
mTmpChildOrderMap.clear();
updateRowStates();
- updateSpeedbump();
+ updateShelfIndex();
updateClearAll();
updateEmptyShadeView();
@@ -1987,8 +1989,8 @@
mNotificationPanel.setShadeEmpty(showEmptyShade);
}
- private void updateSpeedbump() {
- int speedbumpIndex = -1;
+ private void updateShelfIndex() {
+ int shelfIndex = -1;
int currentIndex = 0;
final int N = mStackScroller.getChildCount();
for (int i = 0; i < N; i++) {
@@ -1998,12 +2000,12 @@
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
- speedbumpIndex = currentIndex;
+ shelfIndex = currentIndex;
break;
}
currentIndex++;
}
- mStackScroller.updateSpeedBumpIndex(speedbumpIndex);
+ mStackScroller.updateShelfIndex(shelfIndex);
}
public static boolean isTopLevelChild(Entry entry) {
@@ -2694,6 +2696,14 @@
mFalsingManager.onScreenOff();
}
+ public NotificationShelf getNotificationShelf() {
+ return mNotificationShelf;
+ }
+
+ public NotificationStackScrollLayout getNotificationScrollLayout() {
+ return mStackScroller;
+ }
+
public boolean isPulsing() {
return mDozeScrimController.isPulsing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index dbe7f96..a948a08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -44,6 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.tuner.TunerService;
@@ -74,6 +75,7 @@
private NotificationIconAreaController mNotificationIconAreaController;
private View mNotificationIconAreaInner;
+ private NotificationShelf mNotificationShelf;
private BatteryMeterView mBatteryMeterView;
private BatteryMeterView mBatteryMeterViewKeyguard;
@@ -123,6 +125,7 @@
mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
+ mNotificationShelf = phoneStatusBar.getNotificationShelf();
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, phoneStatusBar);
mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 81da672..9a3dbc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.stack;
+import android.content.Context;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
@@ -33,7 +36,7 @@
private ActivatableNotificationView mActivatedChild;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
- private int mSpeedBumpIndex = -1;
+ private int mShelfIndex = -1;
private boolean mDark;
private boolean mHideSensitive;
private HeadsUpManager mHeadsUpManager;
@@ -44,6 +47,37 @@
private float mMaxHeadsUpTranslation;
private boolean mDismissAllInProgress;
private int mLayoutMinHeight;
+ private NotificationShelf mShelf;
+ private int mZDistanceBetweenElements;
+ private int mBaseZHeight;
+
+ public AmbientState(Context context) {
+ reload(context);
+ }
+
+ /**
+ * Reload the dimens e.g. if the density changed.
+ */
+ public void reload(Context context) {
+ mZDistanceBetweenElements = Math.max(1, context.getResources()
+ .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
+ mBaseZHeight = (StackScrollAlgorithm.MAX_ITEMS_IN_BOTTOM_STACK + 1)
+ * mZDistanceBetweenElements;
+ }
+
+ /**
+ * @return the basic Z height on which notifications remain.
+ */
+ public int getBaseZHeight() {
+ return mBaseZHeight;
+ }
+
+ /**
+ * @return the distance in Z between two overlaying notifications.
+ */
+ public int getZDistanceBetweenElements() {
+ return mZDistanceBetweenElements;
+ }
public int getScrollY() {
return mScrollY;
@@ -118,12 +152,12 @@
return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
}
- public int getSpeedBumpIndex() {
- return mSpeedBumpIndex;
+ public int getShelfIndex() {
+ return mShelfIndex;
}
- public void setSpeedBumpIndex(int speedBumpIndex) {
- mSpeedBumpIndex = speedBumpIndex;
+ public void setShelfIndex(int shelfIndex) {
+ mShelfIndex = shelfIndex;
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -181,4 +215,12 @@
public void setLayoutMinHeight(int layoutMinHeight) {
mLayoutMinHeight = layoutMinHeight;
}
+
+ public void setShelf(NotificationShelf shelf) {
+ mShelf = shelf;
+ }
+
+ public NotificationShelf getShelf() {
+ return mShelf;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
index 02b7ce3..c78def4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -59,15 +59,15 @@
public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
/**
- * The view isn't layouted at all.
- * */
+ * The view isn't laid out at all.
+ */
public static final int LOCATION_GONE = 0x40;
public int height;
public boolean dimmed;
public boolean dark;
public boolean hideSensitive;
- public boolean belowSpeedBump;
+ public boolean belowShelf;
public float shadowAlpha;
/**
@@ -103,7 +103,7 @@
shadowAlpha = svs.shadowAlpha;
dark = svs.dark;
hideSensitive = svs.hideSensitive;
- belowSpeedBump = svs.belowSpeedBump;
+ belowShelf = svs.belowShelf;
clipTopAmount = svs.clipTopAmount;
notGoneIndex = svs.notGoneIndex;
location = svs.location;
@@ -144,7 +144,7 @@
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
// apply below shelf speedBump
- expandableView.setBelowSpeedBump(this.belowSpeedBump);
+ expandableView.setBelowShelf(this.belowShelf);
// apply dark
expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 4b944d1..017c950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -473,7 +473,7 @@
childState.dimmed = parentState.dimmed;
childState.dark = parentState.dark;
childState.hideSensitive = parentState.hideSensitive;
- childState.belowSpeedBump = parentState.belowSpeedBump;
+ childState.belowShelf = parentState.belowShelf;
childState.clipTopAmount = 0;
childState.alpha = 0;
if (i < firstOverflowIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index a8c082e..74ca2032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -69,7 +69,7 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSettingsIconRow;
import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -151,7 +151,7 @@
* The current State this Layout is in
*/
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
- private AmbientState mAmbientState = new AmbientState();
+ private final AmbientState mAmbientState;
private NotificationGroupManager mGroupManager;
private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
@@ -261,7 +261,6 @@
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
- private NotificationOverflowContainer mOverflowContainer;
private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
private FalsingManager mFalsingManager;
private boolean mAnimationRunning;
@@ -354,6 +353,7 @@
private boolean mQsExpanded;
private boolean mForwardScrollable;
private boolean mBackwardScrollable;
+ private NotificationShelf mShelf;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -370,6 +370,7 @@
public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mAmbientState = new AmbientState(context);
mBgColor = context.getColor(R.color.notification_shade_background_color);
int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
@@ -462,6 +463,7 @@
mBottomStackPeekSize = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
mStackScrollAlgorithm.initView(context);
+ mAmbientState.reload(context);
mPaddingBetweenElements = Math.max(1, context.getResources()
.getDimensionPixelSize(R.dimen.notification_divider_height));
mIncreasedPaddingBetweenElements = context.getResources()
@@ -529,8 +531,10 @@
}
}
- public void updateSpeedBumpIndex(int newIndex) {
- mAmbientState.setSpeedBumpIndex(newIndex);
+ public void updateShelfIndex(int newIndex) {
+ mAmbientState.setShelfIndex(newIndex);
+ int iconIndex = newIndex == -1 ? getChildCount() - 3 : newIndex;
+ changeViewPosition(mShelf, iconIndex);
}
public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -1845,7 +1849,8 @@
float previousIncreasedAmount = 0.0f;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
- if (expandableView.getVisibility() != View.GONE) {
+ if (expandableView.getVisibility() != View.GONE
+ && !expandableView.hasNoContentHeight()) {
float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
if (height != 0) {
height += (int) NotificationUtils.interpolate(
@@ -2536,7 +2541,7 @@
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i);
boolean notGone = child.getVisibility() != View.GONE;
- if (notGone) {
+ if (notGone && !child.hasNoContentHeight()) {
float increasedPaddingAmount = child.getIncreasedPaddingAmount();
if (position != 0) {
position += (int) NotificationUtils.interpolate(
@@ -3538,50 +3543,6 @@
}
}
- public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
- int index = -1;
- if (mOverflowContainer != null) {
- index = indexOfChild(mOverflowContainer);
- removeView(mOverflowContainer);
- }
- mOverflowContainer = overFlowContainer;
- addView(mOverflowContainer, index);
- }
-
- public void updateOverflowContainerVisibility(boolean visible) {
- int oldVisibility = mOverflowContainer.willBeGone() ? GONE
- : mOverflowContainer.getVisibility();
- final int newVisibility = visible ? VISIBLE : GONE;
- if (oldVisibility != newVisibility) {
- Runnable onFinishedRunnable = new Runnable() {
- @Override
- public void run() {
- mOverflowContainer.setVisibility(newVisibility);
- mOverflowContainer.setWillBeGone(false);
- updateContentHeight();
- notifyHeightChangeListener(mOverflowContainer);
- }
- };
- if (!mAnimationsEnabled || !mIsExpanded) {
- mOverflowContainer.cancelAppearDrawing();
- onFinishedRunnable.run();
- } else if (newVisibility != GONE) {
- mOverflowContainer.performAddAnimation(0,
- StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mOverflowContainer.setVisibility(newVisibility);
- mOverflowContainer.setWillBeGone(false);
- updateContentHeight();
- notifyHeightChangeListener(mOverflowContainer);
- } else {
- mOverflowContainer.performRemoveAnimation(
- StackStateAnimator.ANIMATION_DURATION_STANDARD,
- 0.0f,
- onFinishedRunnable);
- mOverflowContainer.setWillBeGone(true);
- }
- }
- }
-
public void updateDismissView(boolean visible) {
int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
int newVisibility = visible ? VISIBLE : GONE;
@@ -3949,6 +3910,21 @@
}
}
+ public void setShelf(NotificationShelf shelf) {
+ mShelf = shelf;
+ int index = -1;
+ if (mShelf != null) {
+ index = indexOfChild(mShelf);
+ removeView(mShelf);
+ }
+ addView(mShelf, index);
+ mAmbientState.setShelf(shelf);
+ }
+
+ public NotificationShelf getNotificationShelf() {
+ return mShelf;
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 3a6d757..4d2ea50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -24,6 +24,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -40,14 +41,12 @@
private static final String LOG_TAG = "StackScrollAlgorithm";
- private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
+ public static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
private int mCollapsedSize;
private int mBottomStackPeekSize;
- private int mZDistanceBetweenElements;
- private int mZBasicHeight;
private StackIndentationFunctor mBottomStackIndentationFunctor;
@@ -76,9 +75,6 @@
.getDimensionPixelSize(R.dimen.notification_min_height);
mBottomStackPeekSize = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
- mZDistanceBetweenElements = Math.max(1, context.getResources()
- .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
- mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
mBottomStackSlowDownLength = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
@@ -107,7 +103,7 @@
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
updateClipping(resultState, algorithmState, ambientState);
- updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+ updateShelfState(resultState, algorithmState, ambientState);
getNotificationChildrenStates(resultState, algorithmState);
}
@@ -123,17 +119,20 @@
}
}
- private void updateSpeedBumpState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
+ private void updateShelfState(StackScrollState resultState,
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
+ int shelfIndex = ambientState.getShelfIndex();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = resultState.getViewStateForView(child);
// The speed bump can also be gone, so equality needs to be taken when comparing
// indices.
- childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex;
+ childViewState.belowShelf = shelfIndex != -1 && i >= shelfIndex;
}
+ NotificationShelf shelf = ambientState.getShelf();
+ shelf.updateState(resultState, algorithmState, ambientState);
}
private void updateClipping(StackScrollState resultState,
@@ -201,7 +200,7 @@
childViewState.hideSensitive = hideSensitive;
boolean isActivatedChild = activatedChild == child;
if (dimmed && isActivatedChild) {
- childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
+ childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
}
}
}
@@ -263,6 +262,9 @@
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
+ if (v == ambientState.getShelf()) {
+ continue;
+ }
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
float increasedPadding = v.getIncreasedPaddingAmount();
if (increasedPadding != 0.0f) {
@@ -345,11 +347,13 @@
if (i == 0) {
updateFirstChildHeight(child, childViewState, childHeight, ambientState);
}
+ int shelfIndex = ambientState.getShelfIndex();
+ boolean belowShelf = shelfIndex != -1 && i >= shelfIndex;
// The y position after this element
float nextYPosition = currentYPosition + childHeight +
paddingAfterChild;
- if (nextYPosition >= bottomStackStart) {
+ if (nextYPosition >= bottomStackStart && belowShelf) {
// Case 1:
// We are in the bottom stack.
if (currentYPosition >= bottomStackStart) {
@@ -368,8 +372,12 @@
// Case 2:
// We are in the regular scroll area.
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
- clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
- ambientState);
+ if (belowShelf) {
+ clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
+ ambientState);
+ } else {
+ clampPositionToShelf(i, childViewState, ambientState);
+ }
}
if (i == 0 && ambientState.getScrollY() <= 0) {
@@ -393,12 +401,7 @@
protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
ExpandableView child) {
- Float paddingValue = algorithmState.increasedPaddingMap.get(child);
- return paddingValue == null
- ? mPaddingBetweenElements
- : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
- mIncreasedPaddingBetweenElements,
- paddingValue);
+ return algorithmState.getPaddingAfterChild(child);
}
private void updateHeadsUpStates(StackScrollState resultState,
@@ -485,6 +488,33 @@
}
}
+ /**
+ * Clamp the height of the child down such that its end is at most on the beginning of
+ * the shelf.
+ *
+ * @param index the index of the view
+ * @param childViewState the view state of the child
+ * @param ambientState the ambient state
+ */
+ private void clampPositionToShelf(int index, ExpandableViewState childViewState,
+ AmbientState ambientState) {
+ int minHeight = ambientState.getShelf().getNotificationMergeSize();
+ int shelfEnd = ambientState.getInnerHeight();
+ int shelfStart = shelfEnd - ambientState.getShelf().getIntrinsicHeight();
+ int maxChildEnd = shelfEnd;
+ if (index != ambientState.getShelfIndex() - 1) {
+ maxChildEnd = shelfStart - mPaddingBetweenElements;
+ }
+ childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
+ if (childViewState.yTranslation + childViewState.height > maxChildEnd) {
+ float newHeight = maxChildEnd - childViewState.yTranslation;
+ if (newHeight < minHeight) {
+ newHeight = Math.min(minHeight, shelfEnd - childViewState.yTranslation);
+ }
+ childViewState.height = (int) newHeight;
+ }
+ }
+
protected int getMaxAllowedChildHeight(View child) {
if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
@@ -586,6 +616,8 @@
AmbientState ambientState) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = resultState.getViewStateForView(child);
+ int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
+ float baseZ = ambientState.getBaseZHeight();
if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
// We are in the bottom stack
float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
@@ -599,13 +631,13 @@
} else {
zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
+ (numItemsAbove - factor) * (1.0f / (1.0f - factor))
- * (mZDistanceBetweenElements
+ * (zDistanceBetweenElements
- FakeShadowView.SHADOW_SIBLING_TRESHOLD);
}
} else {
- zSubtraction = numItemsAbove * mZDistanceBetweenElements;
+ zSubtraction = numItemsAbove * zDistanceBetweenElements;
}
- childViewState.zTranslation = mZBasicHeight - zSubtraction;
+ childViewState.zTranslation = baseZ - zSubtraction;
} else if (child.mustStayOnScreen()
&& childViewState.yTranslation < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
@@ -616,10 +648,10 @@
+ ambientState.getStackTranslation() - childViewState.yTranslation;
childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
}
- childViewState.zTranslation = mZBasicHeight
- + childrenOnTop * mZDistanceBetweenElements;
+ childViewState.zTranslation = baseZ
+ + childrenOnTop * zDistanceBetweenElements;
} else {
- childViewState.zTranslation = mZBasicHeight;
+ childViewState.zTranslation = baseZ;
}
}
@@ -646,7 +678,7 @@
this.mIsExpanded = isExpanded;
}
- protected class StackScrollAlgorithmState {
+ public class StackScrollAlgorithmState {
/**
* The scroll position of the algorithm
@@ -673,6 +705,15 @@
* no increased padding, a value of 1 means full padding.
*/
public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
+
+ public int getPaddingAfterChild(ExpandableView child) {
+ Float paddingValue = increasedPaddingMap.get(child);
+ return paddingValue == null
+ ? mPaddingBetweenElements
+ : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ paddingValue);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 1dc09f1..7cf9ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -79,7 +79,10 @@
viewState.alpha = 1f;
viewState.shadowAlpha = 1f;
viewState.notGoneIndex = -1;
+ viewState.xTranslation = view.getTranslationX();
viewState.hidden = false;
+ viewState.scaleX = view.getScaleX();
+ viewState.scaleY = view.getScaleY();
}
public ExpandableViewState getViewStateForView(View requestedView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 0009a04..3ba7774 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -247,8 +247,8 @@
// start dimmed animation
child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
- // apply speed bump state
- child.setBelowSpeedBump(viewState.belowSpeedBump);
+ // apply below shelf state
+ child.setBelowShelf(viewState.belowShelf);
// start hiding sensitive animation
child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 2fead7e..108f0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -28,25 +28,34 @@
public class ViewState {
public float alpha;
+ public float xTranslation;
public float yTranslation;
public float zTranslation;
public boolean gone;
public boolean hidden;
+ public float scaleX = 1.0f;
+ public float scaleY = 1.0f;
public void copyFrom(ViewState viewState) {
alpha = viewState.alpha;
+ xTranslation = viewState.xTranslation;
yTranslation = viewState.yTranslation;
zTranslation = viewState.zTranslation;
gone = viewState.gone;
hidden = viewState.hidden;
+ scaleX = viewState.scaleX;
+ scaleY = viewState.scaleY;
}
public void initFrom(View view) {
alpha = view.getAlpha();
+ xTranslation = view.getTranslationX();
yTranslation = view.getTranslationY();
zTranslation = view.getTranslationZ();
gone = view.getVisibility() == View.GONE;
hidden = false;
+ scaleX = view.getScaleX();
+ scaleY = view.getScaleY();
}
/**
@@ -57,17 +66,10 @@
// don't do anything with it
return;
}
- float alpha = view.getAlpha();
- float yTranslation = view.getTranslationY();
- float xTranslation = view.getTranslationX();
- float zTranslation = view.getTranslationZ();
- float newAlpha = this.alpha;
- float newYTranslation = this.yTranslation;
- float newZTranslation = this.zTranslation;
- boolean becomesInvisible = newAlpha == 0.0f || this.hidden;
- if (alpha != newAlpha && xTranslation == 0) {
+ boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
+ if (view.getAlpha() != this.alpha) {
// apply layer type
- boolean becomesFullyVisible = newAlpha == 1.0f;
+ boolean becomesFullyVisible = this.alpha == 1.0f;
boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
&& view.hasOverlappingRendering();
int layerType = view.getLayerType();
@@ -79,7 +81,7 @@
}
// apply alpha
- view.setAlpha(newAlpha);
+ view.setAlpha(this.alpha);
}
// apply visibility
@@ -92,14 +94,29 @@
}
}
+ // apply xTranslation
+ if (view.getTranslationX() != this.xTranslation) {
+ view.setTranslationX(this.xTranslation);
+ }
+
// apply yTranslation
- if (yTranslation != newYTranslation) {
- view.setTranslationY(newYTranslation);
+ if (view.getTranslationY() != this.yTranslation) {
+ view.setTranslationY(this.yTranslation);
}
// apply zTranslation
- if (zTranslation != newZTranslation) {
- view.setTranslationZ(newZTranslation);
+ if (view.getTranslationZ() != this.zTranslation) {
+ view.setTranslationZ(this.zTranslation);
+ }
+
+ // apply scaleX
+ if (view.getScaleX() != this.scaleX) {
+ view.setScaleX(this.scaleX);
+ }
+
+ // apply scaleY
+ if (view.getScaleY() != this.scaleY) {
+ view.setScaleY(this.scaleY);
}
}
}