OSDN Git Service

Split NotificationViewHierarchyManager out of StatusBar.
authorEliot Courtney <edcourtney@google.com>
Mon, 27 Nov 2017 04:27:46 +0000 (13:27 +0900)
committerEliot Courtney <edcourtney@google.com>
Tue, 26 Dec 2017 07:43:53 +0000 (16:43 +0900)
NotificationViewHierarchyManager handles bundling and unbundling of
notifications. In doing so, which notifications are parents/children of
the other can change. NotificationViewHierarchyManager makes sure the
view hierarchy of the notifications matches their grouping.

Bug: 63874929
Bug: 62602530
Test: runtest systemui
Test: Compile and run
Change-Id: Ia1c8ed75d4eb8df52897c5d6aa0713f8335b2a19

16 files changed:
packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java [new file with mode: 0644]
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java

index c32e089..47148a4 100644 (file)
@@ -32,12 +32,14 @@ import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -122,6 +124,7 @@ public class SystemUIFactory {
             Context context) {
         providers.put(NotificationLockscreenUserManager.class,
                 () -> new NotificationLockscreenUserManager(context));
+        providers.put(VisualStabilityManager.class, VisualStabilityManager::new);
         providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
         providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
         providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
@@ -134,6 +137,12 @@ public class SystemUIFactory {
         providers.put(NotificationLogger.class, () -> new NotificationLogger(
                 Dependency.get(NotificationListener.class),
                 Dependency.get(UiOffloadThread.class)));
+        providers.put(NotificationViewHierarchyManager.class, () ->
+                new NotificationViewHierarchyManager(
+                        Dependency.get(NotificationLockscreenUserManager.class),
+                        Dependency.get(NotificationGroupManager.class),
+                        Dependency.get(VisualStabilityManager.class),
+                        context));
         providers.put(NotificationEntryManager.class, () ->
                 new NotificationEntryManager(
                         Dependency.get(NotificationLockscreenUserManager.class),
@@ -145,9 +154,8 @@ public class SystemUIFactory {
                         Dependency.get(NotificationListener.class),
                         Dependency.get(MetricsLogger.class),
                         Dependency.get(DeviceProvisionedController.class),
+                        Dependency.get(VisualStabilityManager.class),
                         Dependency.get(UiOffloadThread.class),
                         context));
-        providers.put(NotificationListener.class, () -> new NotificationListener(
-                Dependency.get(NotificationRemoteInputManager.class), context));
     }
 }
index 37172b6..20418c3 100644 (file)
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -43,10 +44,11 @@ public class CarNotificationEntryManager extends NotificationEntryManager {
             NotificationListener notificationListener,
             MetricsLogger metricsLogger,
             DeviceProvisionedController deviceProvisionedController,
+            VisualStabilityManager visualStabilityManager,
             UiOffloadThread uiOffloadThread, Context context) {
         super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager, mediaManager,
                 foregroundServiceController, notificationListener, metricsLogger,
-                deviceProvisionedController, uiOffloadThread, context);
+                deviceProvisionedController, visualStabilityManager, uiOffloadThread, context);
     }
 
     /**
index 24f8b97..55965e7 100644 (file)
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.volume.car.CarVolumeDialogController;
@@ -54,6 +55,7 @@ public class CarSystemUIFactory extends SystemUIFactory {
                 Dependency.get(NotificationListener.class),
                 Dependency.get(MetricsLogger.class),
                 Dependency.get(DeviceProvisionedController.class),
+                Dependency.get(VisualStabilityManager.class),
                 Dependency.get(UiOffloadThread.class),
                 context));
     }
index b60d2ac..90b46c8 100644 (file)
@@ -60,7 +60,6 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.util.leak.LeakDetector;
 
 import java.io.FileDescriptor;
@@ -75,7 +74,8 @@ import java.util.List;
  * Notification.*Manager objects.
  */
 public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
-        ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler {
+        ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
+        VisualStabilityManager.Callback {
     private static final String TAG = "NotificationEntryManager";
     protected static final boolean DEBUG = false;
     protected static final boolean ENABLE_HEADS_UP = true;
@@ -90,6 +90,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
     protected final NotificationMediaManager mMediaManager;
     protected final MetricsLogger mMetricsLogger;
     protected final DeviceProvisionedController mDeviceProvisionedController;
+    protected final VisualStabilityManager mVisualStabilityManager;
     protected final UiOffloadThread mUiOffloadThread;
     protected final ForegroundServiceController mForegroundServiceController;
     protected final NotificationListener mNotificationListener;
@@ -100,7 +101,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
     protected IStatusBarService mBarService;
     protected NotificationPresenter mPresenter;
     protected Callback mCallback;
-    protected NotificationStackScrollLayout mStackScroller;
     protected PowerManager mPowerManager;
     protected SystemServicesProxy mSystemServicesProxy;
     protected NotificationListenerService.RankingMap mLatestRankingMap;
@@ -109,7 +109,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
     protected ContentObserver mHeadsUpObserver;
     protected boolean mUseHeadsUp = false;
     protected boolean mDisableNotificationAlerts;
-    protected VisualStabilityManager mVisualStabilityManager;
+    protected NotificationListContainer mListContainer;
 
     private final class NotificationClicker implements View.OnClickListener {
 
@@ -214,6 +214,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
             NotificationListener notificationListener,
             MetricsLogger metricsLogger,
             DeviceProvisionedController deviceProvisionedController,
+            VisualStabilityManager visualStabilityManager,
             UiOffloadThread uiOffloadThread, Context context) {
         mLockscreenUserManager = lockscreenUserManager;
         mGroupManager = groupManager;
@@ -224,6 +225,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         mNotificationListener = notificationListener;
         mMetricsLogger = metricsLogger;
         mDeviceProvisionedController = deviceProvisionedController;
+        mVisualStabilityManager = visualStabilityManager;
         mUiOffloadThread = uiOffloadThread;
         mContext = context;
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -233,19 +235,15 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
     }
 
-    // TODO: Remove dependency on NotificationStackScrollLayout
     public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller,
-            Callback callback,
-            VisualStabilityManager visualStabilityManager,
+            NotificationListContainer listContainer, Callback callback,
             HeadsUpManager headsUpManager) {
         mPresenter = presenter;
         mCallback = callback;
-        mStackScroller = stackScroller;
-        mVisualStabilityManager = visualStabilityManager;
         mNotificationData = new NotificationData(presenter);
         mHeadsUpManager = headsUpManager;
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mListContainer = listContainer;
 
         mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
             @Override
@@ -301,6 +299,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         });
     }
 
+    @Override
+    public void onReorderingAllowed() {
+        updateNotifications();
+    }
+
     private boolean shouldSuppressFullScreenIntent(String key) {
         if (mPresenter.isDeviceInVrMode()) {
             return true;
@@ -379,7 +382,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
             int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
             if (isHeadsUp(n.getKey())) {
                 dismissalSurface = NotificationStats.DISMISSAL_PEEK;
-            } else if (mStackScroller.hasPulsingNotifications()) {
+            } else if (mListContainer.hasPulsingNotifications()) {
                 dismissalSurface = NotificationStats.DISMISSAL_AOD;
             }
             mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
@@ -536,7 +539,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
 
         if (entry != null && entry.row != null) {
             entry.row.setRemoved();
-            mStackScroller.cleanUpViewState(entry.row);
+            mListContainer.cleanUpViewState(entry.row);
         }
         // Let's remove the children if this was a summary
         handleGroupSummaryRemoved(key);
@@ -662,7 +665,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         Dependency.get(LeakDetector.class).trackInstance(entry);
         entry.createIcons(mContext, sbn);
         // Construct the expanded view.
-        inflateViews(entry, mStackScroller);
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
         return entry;
     }
 
@@ -752,7 +755,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         mGroupManager.onEntryUpdated(entry, oldNotification);
 
         entry.updateIcons(mContext, notification);
-        inflateViews(entry, mStackScroller);
+        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
 
         mForegroundServiceController.updateNotification(notification,
                 mNotificationData.getImportance(key));
@@ -766,7 +769,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
         if (!notification.isClearable()) {
             // The user may have performed a dismiss action on the notification, since it's
             // not clearable we should snap it back.
-            mStackScroller.snapViewIfNeeded(entry.row);
+            mListContainer.snapViewIfNeeded(entry.row);
         }
 
         if (DEBUG) {
index d6a8af5..0cecadf 100644 (file)
@@ -42,7 +42,6 @@ import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.io.FileDescriptor;
@@ -76,7 +75,7 @@ public class NotificationGutsManager implements Dumpable {
 
     // TODO: Create NotificationListContainer interface and use it instead of
     // NotificationStackScrollLayout here
-    private NotificationStackScrollLayout mStackScroller;
+    private NotificationListContainer mListContainer;
     private NotificationInfo.CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
@@ -96,12 +95,11 @@ public class NotificationGutsManager implements Dumpable {
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
 
-    public void setUp(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller,
+    public void setUp(NotificationPresenter presenter, NotificationListContainer listContainer,
             NotificationInfo.CheckSaveListener checkSaveListener,
             OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
-        mStackScroller = stackScroller;
+        mListContainer = listContainer;
         mCheckSaveListener = checkSaveListener;
         mOnSettingsClickListener = onSettingsClickListener;
     }
@@ -158,7 +156,7 @@ public class NotificationGutsManager implements Dumpable {
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
             if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(
+                mListContainer.onHeightChanged(
                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
             }
             if (mNotificationGutsExposed == g) {
@@ -176,11 +174,11 @@ public class NotificationGutsManager implements Dumpable {
         View gutsView = item.getGutsView();
         if (gutsView instanceof NotificationSnooze) {
             NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
             snoozeGuts.setStatusBarNotification(sbn);
             snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
             guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+                mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
             });
         }
 
@@ -258,7 +256,7 @@ public class NotificationGutsManager implements Dumpable {
             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
         }
         if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+            mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
         }
     }
 
@@ -351,7 +349,7 @@ public class NotificationGutsManager implements Dumpable {
                                 !mAccessibilityManager.isTouchExplorationEnabled());
                 guts.setExposed(true /* exposed */, needsFalsingProtection);
                 row.closeRemoteInput();
-                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mListContainer.onHeightChanged(row, true /* needsAnimation */);
                 mNotificationGutsExposed = guts;
                 mGutsMenuItem = item;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
new file mode 100644 (file)
index 0000000..43be44d
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 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.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+
+/**
+ * Interface representing the entity that contains notifications. It can have
+ * notification views added and removed from it, and will manage displaying them to the user.
+ */
+public interface NotificationListContainer {
+
+    /**
+     * Called when a child is being transferred.
+     *
+     * @param childTransferInProgress whether child transfer is in progress
+     */
+    void setChildTransferInProgress(boolean childTransferInProgress);
+
+    /**
+     * Change the position of child to a new location
+     *
+     * @param child the view to change the position for
+     * @param newIndex the new index
+     */
+    void changeViewPosition(View child, int newIndex);
+
+    /**
+     * Called when a child was added to a group.
+     *
+     * @param row row of the group child that was added
+     */
+    void notifyGroupChildAdded(View row);
+
+    /**
+     * Called when a child was removed from a group.
+     *
+     * @param row row of the child that was removed
+     * @param childrenContainer ViewGroup of the group that the child was removed from
+     */
+    void notifyGroupChildRemoved(View row, ViewGroup childrenContainer);
+
+    /**
+     * Generate an animation for an added child view.
+     *
+     * @param child The view to be added.
+     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
+     */
+    void generateAddAnimation(View child, boolean fromMoreCard);
+
+    /**
+     * Generate a child order changed event.
+     */
+    void generateChildOrderChangedEvent();
+
+    /**
+     * Returns the number of children in the NotificationListContainer.
+     *
+     * @return the number of children in the NotificationListContainer
+     */
+    int getContainerChildCount();
+
+    /**
+     * Gets the ith child in the NotificationListContainer.
+     *
+     * @param i ith child to get
+     * @return the ith child in the list container
+     */
+    View getContainerChildAt(int i);
+
+    /**
+     * Remove a view from the container
+     *
+     * @param v view to remove
+     */
+    void removeContainerView(View v);
+
+    /**
+     * Add a view to the container
+     *
+     * @param v view to add
+     */
+    void addContainerView(View v);
+
+    /**
+     * Sets the maximum number of notifications to display.
+     *
+     * @param maxNotifications max number of notifications to display
+     */
+    void setMaxDisplayedNotifications(int maxNotifications);
+
+    /**
+     * Handle snapping a non-dismissable row back if the user tried to dismiss it.
+     *
+     * @param row row to snap back
+     */
+    void snapViewIfNeeded(ExpandableNotificationRow row);
+
+    /**
+     * Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
+     *
+     * @param entry entry to get the view parent for
+     * @return the view parent for entry
+     */
+    ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+
+    /**
+     * Called when the height of an expandable view changes.
+     *
+     * @param view view whose height changed
+     * @param animate whether this change should be animated
+     */
+    void onHeightChanged(ExpandableView view, boolean animate);
+
+    /**
+     * Resets the currently exposed menu view.
+     *
+     * @param animate whether to animate the closing/change of menu view
+     * @param force reset the menu view even if it looks like it is already reset
+     */
+    void resetExposedMenuView(boolean animate, boolean force);
+
+    /**
+     * Returns the NotificationSwipeActionHelper for the NotificationListContainer.
+     *
+     * @return swipe action helper for the list container
+     */
+    NotificationSwipeActionHelper getSwipeActionHelper();
+
+    /**
+     * Called when a notification is removed from the shade. This cleans up the state for a
+     * given view.
+     *
+     * @param view view to clean up view state for
+     */
+    void cleanUpViewState(View view);
+
+    /**
+     * Returns whether an ExpandableNotificationRow is in a visible location or not.
+     *
+     * @param row
+     * @return true if row is in a visible location
+     */
+    boolean isInVisibleLocation(ExpandableNotificationRow row);
+
+    /**
+     * Sets a listener to listen for changes in notification locations.
+     *
+     * @param listener listener to set
+     */
+    void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener);
+
+    /**
+     * Called when an update to the notification view hierarchy is completed.
+     */
+    default void onNotificationViewUpdateFinished() {}
+
+    /**
+     * Returns true if there are pulsing notifications.
+     *
+     * @return true if has pulsing notifications
+     */
+    boolean hasPulsingNotifications();
+}
index e958f3f..9ff7c18 100644 (file)
@@ -28,7 +28,6 @@ import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -54,14 +53,12 @@ public class NotificationLogger {
     protected Handler mHandler = new Handler();
     protected IStatusBarService mBarService;
     private long mLastVisibilityReportUptimeMs;
-    private NotificationStackScrollLayout mStackScroller;
+    private NotificationListContainer mListContainer;
 
-    protected final NotificationStackScrollLayout.OnChildLocationsChangedListener
-            mNotificationLocationsChangedListener =
-            new NotificationStackScrollLayout.OnChildLocationsChangedListener() {
+    protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
+            new OnChildLocationsChangedListener() {
                 @Override
-                public void onChildLocationsChanged(
-                        NotificationStackScrollLayout stackScrollLayout) {
+                public void onChildLocationsChanged() {
                     if (mHandler.hasCallbacks(mVisibilityReporter)) {
                         // Visibilities will be reported when the existing
                         // callback is executed.
@@ -105,7 +102,7 @@ public class NotificationLogger {
             for (int i = 0; i < N; i++) {
                 NotificationData.Entry entry = activeNotifications.get(i);
                 String key = entry.notification.getKey();
-                boolean isVisible = mStackScroller.isInVisibleLocation(entry.row);
+                boolean isVisible = mListContainer.isInVisibleLocation(entry.row);
                 NotificationVisibility visObj = NotificationVisibility.obtain(key, i, isVisible);
                 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
                 if (isVisible) {
@@ -143,11 +140,10 @@ public class NotificationLogger {
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
-    // TODO: Remove dependency on NotificationStackScrollLayout.
     public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationStackScrollLayout stackScroller) {
+            NotificationListContainer listContainer) {
         mPresenter = presenter;
-        mStackScroller = stackScroller;
+        mListContainer = listContainer;
     }
 
     public void stopNotificationLogging() {
@@ -159,18 +155,18 @@ public class NotificationLogger {
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
         }
         mHandler.removeCallbacks(mVisibilityReporter);
-        mStackScroller.setChildLocationsChangedListener(null);
+        mListContainer.setChildLocationsChangedListener(null);
     }
 
     public void startNotificationLogging() {
-        mStackScroller.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
+        mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener);
         // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
         // cause the scroller to emit child location events. Hence generate
         // one ourselves to guarantee that we're reporting visible
         // notifications.
         // (Note that in cases where the scroller does emit events, this
         // additional event doesn't break anything.)
-        mNotificationLocationsChangedListener.onChildLocationsChanged(mStackScroller);
+        mNotificationLocationsChangedListener.onChildLocationsChanged();
     }
 
     private void logNotificationVisibilityChanges(
@@ -220,4 +216,11 @@ public class NotificationLogger {
     public Runnable getVisibilityReporter() {
         return mVisibilityReporter;
     }
+
+    /**
+     * A listener that is notified when some child locations might have changed.
+     */
+    public interface OnChildLocationsChangedListener {
+        void onChildLocationsChanged();
+    }
 }
index c1dd958..ec1a8ba 100644 (file)
@@ -104,7 +104,6 @@ public interface NotificationPresenter extends NotificationData.Environment,
      */
     NotificationEntryManager getEntryManager();
 
-    // TODO: Remove this once the view managing code is pulled out of StatusBar.
     /**
      * Updates the visual representation of the notifications.
      */
@@ -114,4 +113,17 @@ public interface NotificationPresenter extends NotificationData.Environment,
      * @return true iff the device is dozing
      */
     boolean isDozing();
+
+    /**
+     * Returns the maximum number of notifications to show while locked.
+     *
+     * @param recompute whether something has changed that means we should recompute this value
+     * @return the maximum number of notifications to show while locked
+     */
+    int getMaxNotificationsWhileLocked(boolean recompute);
+
+    /**
+     * Called when the row states are updated by NotificationViewHierarchyManager.
+     */
+    void onUpdateRowStates();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
new file mode 100644 (file)
index 0000000..b73b9b6
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2017 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.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
+ * on their group structure. For example, if a notification becomes bundled with another,
+ * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
+ * tell NotificationListContainer which notifications to display, and inform it of changes to those
+ * notifications that might affect their display.
+ */
+public class NotificationViewHierarchyManager {
+    private static final String TAG = "NotificationViewHierarchyManager";
+
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
+    protected final NotificationLockscreenUserManager mLockscreenUserManager;
+    protected final NotificationGroupManager mGroupManager;
+    protected final VisualStabilityManager mVisualStabilityManager;
+
+    /**
+     * {@code true} if notifications not part of a group should by default be rendered in their
+     * expanded state. If {@code false}, then only the first notification will be expanded if
+     * possible.
+     */
+    private final boolean mAlwaysExpandNonGroupedNotification;
+
+    private NotificationPresenter mPresenter;
+    private NotificationEntryManager mEntryManager;
+    private NotificationListContainer mListContainer;
+
+    public NotificationViewHierarchyManager(
+            NotificationLockscreenUserManager lockscreenUserManager,
+            NotificationGroupManager groupManager,
+            VisualStabilityManager visualStabilityManager,
+            Context context) {
+        mLockscreenUserManager = lockscreenUserManager;
+        mGroupManager = groupManager;
+        mVisualStabilityManager = visualStabilityManager;
+
+        Resources res = context.getResources();
+        mAlwaysExpandNonGroupedNotification =
+                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
+    }
+
+    public void setUpWithPresenter(NotificationPresenter presenter,
+            NotificationEntryManager entryManager, NotificationListContainer listContainer) {
+        mPresenter = presenter;
+        mEntryManager = entryManager;
+        mListContainer = listContainer;
+    }
+
+    /**
+     * Updates the visual representation of the notifications.
+     */
+    public void updateNotificationViews() {
+        ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+                .getActiveNotifications();
+        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
+        final int N = activeNotifications.size();
+        for (int i = 0; i < N; i++) {
+            NotificationData.Entry ent = activeNotifications.get(i);
+            if (ent.row.isDismissed() || ent.row.isRemoved()) {
+                // we don't want to update removed notifications because they could
+                // temporarily become children if they were isolated before.
+                continue;
+            }
+            int userId = ent.notification.getUserId();
+
+            // Display public version of the notification if we need to redact.
+            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
+            // We can probably move some of this code there.
+            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
+                    mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
+            boolean sensitive = userPublic && needsRedaction;
+            boolean deviceSensitive = devicePublic
+                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                    mLockscreenUserManager.getCurrentUserId());
+            ent.row.setSensitive(sensitive, deviceSensitive);
+            ent.row.setNeedsRedaction(needsRedaction);
+            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
+                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
+                        ent.row.getStatusBarNotification());
+                List<ExpandableNotificationRow> orderedChildren =
+                        mTmpChildOrderMap.get(summary);
+                if (orderedChildren == null) {
+                    orderedChildren = new ArrayList<>();
+                    mTmpChildOrderMap.put(summary, orderedChildren);
+                }
+                orderedChildren.add(ent.row);
+            } else {
+                toShow.add(ent.row);
+            }
+
+        }
+
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
+                toRemove.add((ExpandableNotificationRow) child);
+            }
+        }
+
+        for (ExpandableNotificationRow remove : toRemove) {
+            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
+                mListContainer.setChildTransferInProgress(true);
+            }
+            if (remove.isSummaryWithChildren()) {
+                remove.removeAllChildren();
+            }
+            mListContainer.removeContainerView(remove);
+            mListContainer.setChildTransferInProgress(false);
+        }
+
+        removeNotificationChildren();
+
+        for (int i = 0; i < toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mVisualStabilityManager.notifyViewAddition(v);
+                mListContainer.addContainerView(v);
+            }
+        }
+
+        addNotificationChildrenAndSort();
+
+        // So after all this work notifications still aren't sorted correctly.
+        // Let's do that now by advancing through toShow and mListContainer in
+        // lock-step, making sure mListContainer matches what we see in toShow.
+        int j = 0;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow targetChild = toShow.get(j);
+            if (child != targetChild) {
+                // Oops, wrong notification at this position. Put the right one
+                // here and advance both lists.
+                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
+                    mListContainer.changeViewPosition(targetChild, i);
+                } else {
+                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+                }
+            }
+            j++;
+
+        }
+
+        mVisualStabilityManager.onReorderingFinished();
+        // clear the map again for the next usage
+        mTmpChildOrderMap.clear();
+
+        updateRowStates();
+
+        mListContainer.onNotificationViewUpdateFinished();
+    }
+
+    private void addNotificationChildrenAndSort() {
+        // Let's now add all notification children which are missing
+        boolean orderChanged = false;
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
+                    childIndex++) {
+                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+                if (children == null || !children.contains(childView)) {
+                    if (childView.getParent() != null) {
+                        Log.wtf(TAG, "trying to add a notification child that already has " +
+                                "a parent. class:" + childView.getParent().getClass() +
+                                "\n child: " + childView);
+                        // This shouldn't happen. We can recover by removing it though.
+                        ((ViewGroup) childView.getParent()).removeView(childView);
+                    }
+                    mVisualStabilityManager.notifyViewAddition(childView);
+                    parent.addChildNotification(childView, childIndex);
+                    mListContainer.notifyGroupChildAdded(childView);
+                }
+            }
+
+            // Finally after removing and adding has been performed we can apply the order.
+            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+                    mEntryManager);
+        }
+        if (orderChanged) {
+            mListContainer.generateChildOrderChangedEvent();
+        }
+    }
+
+    private void removeNotificationChildren() {
+        // First let's remove all children which don't belong in the parents
+        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
+            View view = mListContainer.getContainerChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                // We don't care about non-notification views.
+                continue;
+            }
+
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+
+            if (children != null) {
+                toRemove.clear();
+                for (ExpandableNotificationRow childRow : children) {
+                    if ((orderedChildren == null
+                            || !orderedChildren.contains(childRow))
+                            && !childRow.keepInParent()) {
+                        toRemove.add(childRow);
+                    }
+                }
+                for (ExpandableNotificationRow remove : toRemove) {
+                    parent.removeChildNotification(remove);
+                    if (mEntryManager.getNotificationData().get(
+                            remove.getStatusBarNotification().getKey()) == null) {
+                        // We only want to add an animation if the view is completely removed
+                        // otherwise it's just a transfer
+                        mListContainer.notifyGroupChildRemoved(remove,
+                                parent.getChildrenContainer());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    public void updateRowStates() {
+        final int N = mListContainer.getContainerChildCount();
+
+        int visibleNotifications = 0;
+        boolean isLocked = mPresenter.isPresenterLocked();
+        int maxNotifications = -1;
+        if (isLocked) {
+            maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
+        }
+        mListContainer.setMaxDisplayedNotifications(maxNotifications);
+        Stack<ExpandableNotificationRow> stack = new Stack<>();
+        for (int i = N - 1; i >= 0; i--) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            stack.push((ExpandableNotificationRow) child);
+        }
+        while(!stack.isEmpty()) {
+            ExpandableNotificationRow row = stack.pop();
+            NotificationData.Entry entry = row.getEntry();
+            boolean isChildNotification =
+                    mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+            row.setOnKeyguard(isLocked);
+
+            if (!isLocked) {
+                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+                // very first notification and if it's not a child of grouped notifications.
+                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+                        || (visibleNotifications == 0 && !isChildNotification
+                        && !row.isLowPriority()));
+            }
+
+            entry.row.setShowAmbient(mPresenter.isDozing());
+            int userId = entry.notification.getUserId();
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    entry.notification) && !entry.row.isRemoved();
+            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
+                    .notification);
+            if (suppressedSummary
+                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
+                    && !mLockscreenUserManager.shouldShowLockscreenNotifications())
+                    || (isLocked && !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+            } else {
+                boolean wasGone = entry.row.getVisibility() == View.GONE;
+                if (wasGone) {
+                    entry.row.setVisibility(View.VISIBLE);
+                }
+                if (!isChildNotification && !entry.row.isRemoved()) {
+                    if (wasGone) {
+                        // notify the scroller of a child addition
+                        mListContainer.generateAddAnimation(entry.row,
+                                !showOnKeyguard /* fromMoreCard */);
+                    }
+                    visibleNotifications++;
+                }
+            }
+            if (row.isSummaryWithChildren()) {
+                List<ExpandableNotificationRow> notificationChildren =
+                        row.getNotificationChildren();
+                int size = notificationChildren.size();
+                for (int i = size - 1; i >= 0; i--) {
+                    stack.push(notificationChildren.get(i));
+                }
+            }
+        }
+
+        mPresenter.onUpdateRowStates();
+    }
+}
index 61dd22f..f0bd1f9 100644 (file)
@@ -455,7 +455,7 @@ public class NotificationPanelView extends PanelView implements
             mTopPaddingAdjustment = 0;
         } else {
             mClockPositionAlgorithm.setup(
-                    mStatusBar.getMaxKeyguardNotifications(),
+                    mStatusBar.getMaxNotificationsWhileLocked(),
                     getMaxPanelHeight(),
                     getExpandedHeight(),
                     mNotificationStackScroller.getNotGoneChildCount(),
index 04fe7f2..72a52d8 100644 (file)
@@ -28,7 +28,6 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager
         .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -198,6 +197,7 @@ import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -233,14 +233,12 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
+        OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
@@ -371,13 +369,6 @@ public class StatusBar extends SystemUI implements DemoMode,
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     private TextView mNotificationPanelDebugText;
 
-    /**
-     * {@code true} if notifications not part of a group should by default be rendered in their
-     * expanded state. If {@code false}, then only the first notification will be expanded if
-     * possible.
-     */
-    private boolean mAlwaysExpandNonGroupedNotification;
-
     // settings
     private QSPanel mQSPanel;
 
@@ -407,6 +398,7 @@ public class StatusBar extends SystemUI implements DemoMode,
     private NotificationGutsManager mGutsManager;
     protected NotificationLogger mNotificationLogger;
     protected NotificationEntryManager mEntryManager;
+    protected NotificationViewHierarchyManager mViewHierarchyManager;
 
     // for disabling the status bar
     private int mDisabled1 = 0;
@@ -577,8 +569,6 @@ public class StatusBar extends SystemUI implements DemoMode,
             goToLockedShade(null);
         }
     };
-    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
-            mTmpChildOrderMap = new HashMap<>();
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
@@ -598,6 +588,7 @@ public class StatusBar extends SystemUI implements DemoMode,
     @Override
     public void start() {
         mGroupManager = Dependency.get(NotificationGroupManager.class);
+        mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
         mNotificationLogger = Dependency.get(NotificationLogger.class);
         mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
         mNotificationListener =  Dependency.get(NotificationListener.class);
@@ -616,6 +607,7 @@ public class StatusBar extends SystemUI implements DemoMode,
         mGutsManager = Dependency.get(NotificationGutsManager.class);
         mMediaManager = Dependency.get(NotificationMediaManager.class);
         mEntryManager = Dependency.get(NotificationEntryManager.class);
+        mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
@@ -628,8 +620,6 @@ public class StatusBar extends SystemUI implements DemoMode,
         Resources res = mContext.getResources();
         mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
-        mAlwaysExpandNonGroupedNotification =
-                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
         putComponent(StatusBar.class, this);
@@ -814,8 +804,8 @@ public class StatusBar extends SystemUI implements DemoMode,
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
-        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mVisualStabilityManager,
-                mHeadsUpManager);
+        mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager);
+        mViewHierarchyManager.setUpWithPresenter(this, mEntryManager, mStackScroller);
 
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
@@ -1361,112 +1351,8 @@ public class StatusBar extends SystemUI implements DemoMode,
             return;
         }
 
-        ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
-                .getActiveNotifications();
-        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
-        final int N = activeNotifications.size();
-        for (int i = 0; i < N; i++) {
-            Entry ent = activeNotifications.get(i);
-            if (ent.row.isDismissed() || ent.row.isRemoved()) {
-                // we don't want to update removed notifications because they could
-                // temporarily become children if they were isolated before.
-                continue;
-            }
-            int userId = ent.notification.getUserId();
-
-            // Display public version of the notification if we need to redact.
-            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
-            // We can probably move some of this code there.
-            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
-                    mLockscreenUserManager.getCurrentUserId());
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
-            boolean sensitive = userPublic && needsRedaction;
-            boolean deviceSensitive = devicePublic
-                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
-                    mLockscreenUserManager.getCurrentUserId());
-            ent.row.setSensitive(sensitive, deviceSensitive);
-            ent.row.setNeedsRedaction(needsRedaction);
-            if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
-                ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
-                        ent.row.getStatusBarNotification());
-                List<ExpandableNotificationRow> orderedChildren =
-                        mTmpChildOrderMap.get(summary);
-                if (orderedChildren == null) {
-                    orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(summary, orderedChildren);
-                }
-                orderedChildren.add(ent.row);
-            } else {
-                toShow.add(ent.row);
-            }
-
-        }
-
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i=0; i< mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                toRemove.add((ExpandableNotificationRow) child);
-            }
-        }
-
-        for (ExpandableNotificationRow remove : toRemove) {
-            if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transferring this notification to its parent, don't generate an
-                // animation
-                mStackScroller.setChildTransferInProgress(true);
-            }
-            if (remove.isSummaryWithChildren()) {
-                remove.removeAllChildren();
-            }
-            mStackScroller.removeView(remove);
-            mStackScroller.setChildTransferInProgress(false);
-        }
-
-        removeNotificationChildren();
-
-        for (int i = 0; i < toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mVisualStabilityManager.notifyViewAddition(v);
-                mStackScroller.addView(v);
-            }
-        }
-
-        addNotificationChildrenAndSort();
-
-        // So after all this work notifications still aren't sorted correctly.
-        // Let's do that now by advancing through toShow and mStackScroller in
-        // lock-step, making sure mStackScroller matches what we see in toShow.
-        int j = 0;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
+        mViewHierarchyManager.updateNotificationViews();
 
-            ExpandableNotificationRow targetChild = toShow.get(j);
-            if (child != targetChild) {
-                // Oops, wrong notification at this position. Put the right one
-                // here and advance both lists.
-                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
-                    mStackScroller.changeViewPosition(targetChild, i);
-                } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(this);
-                }
-            }
-            j++;
-
-        }
-
-        mVisualStabilityManager.onReorderingFinished();
-        // clear the map again for the next usage
-        mTmpChildOrderMap.clear();
-
-        updateRowStates();
         updateSpeedBumpIndex();
         updateClearAll();
         updateEmptyShadeView();
@@ -1521,82 +1407,6 @@ public class StatusBar extends SystemUI implements DemoMode,
                 && !ONLY_CORE_APPS);
     }
 
-    private void addNotificationChildrenAndSort() {
-        // Let's now add all notification children which are missing
-        boolean orderChanged = false;
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
-                    childIndex++) {
-                ExpandableNotificationRow childView = orderedChildren.get(childIndex);
-                if (children == null || !children.contains(childView)) {
-                    if (childView.getParent() != null) {
-                        Log.wtf(TAG, "trying to add a notification child that already has " +
-                                "a parent. class:" + childView.getParent().getClass() +
-                                "\n child: " + childView);
-                        // This shouldn't happen. We can recover by removing it though.
-                        ((ViewGroup) childView.getParent()).removeView(childView);
-                    }
-                    mVisualStabilityManager.notifyViewAddition(childView);
-                    parent.addChildNotification(childView, childIndex);
-                    mStackScroller.notifyGroupChildAdded(childView);
-                }
-            }
-
-            // Finally after removing and adding has been performed we can apply the order.
-            orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
-        }
-        if (orderChanged) {
-            mStackScroller.generateChildOrderChangedEvent();
-        }
-    }
-
-    private void removeNotificationChildren() {
-        // First let's remove all children which don't belong in the parents
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i = 0; i < mStackScroller.getChildCount(); i++) {
-            View view = mStackScroller.getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getNotificationChildren();
-            List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
-
-            if (children != null) {
-                toRemove.clear();
-                for (ExpandableNotificationRow childRow : children) {
-                    if ((orderedChildren == null
-                            || !orderedChildren.contains(childRow))
-                            && !childRow.keepInParent()) {
-                        toRemove.add(childRow);
-                    }
-                }
-                for (ExpandableNotificationRow remove : toRemove) {
-                    parent.removeChildNotification(remove);
-                    if (mEntryManager.getNotificationData().get(
-                            remove.getStatusBarNotification().getKey()) == null) {
-                        // We only want to add an animation if the view is completely removed
-                        // otherwise it's just a transfer
-                        mStackScroller.notifyGroupChildRemoved(remove,
-                                parent.getChildrenContainer());
-                    }
-                }
-            }
-        }
-    }
-
     public void addQsTile(ComponentName tile) {
         mQSPanel.getHost().addTile(tile);
     }
@@ -2171,11 +1981,6 @@ public class StatusBar extends SystemUI implements DemoMode,
         return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
-    @Override
-    public void onReorderingAllowed() {
-        mEntryManager.updateNotifications();
-    }
-
     public boolean isLaunchTransitionFadingAway() {
         return mLaunchTransitionFadingAway;
     }
@@ -3146,7 +2951,7 @@ public class StatusBar extends SystemUI implements DemoMode,
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
         }
 
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         mScreenPinningRequest.onConfigurationChanged();
     }
 
@@ -3919,7 +3724,7 @@ public class StatusBar extends SystemUI implements DemoMode,
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        updateRowStates();
+        mViewHierarchyManager.updateRowStates();
         Trace.endSection();
     }
 
@@ -4123,7 +3928,8 @@ public class StatusBar extends SystemUI implements DemoMode,
         }
     }
 
-    protected int getMaxKeyguardNotifications(boolean recompute) {
+    @Override
+    public int getMaxNotificationsWhileLocked(boolean recompute) {
         if (recompute) {
             mMaxKeyguardNotifications = Math.max(1,
                     mNotificationPanel.computeMaxKeyguardNotifications(
@@ -4133,8 +3939,8 @@ public class StatusBar extends SystemUI implements DemoMode,
         return mMaxKeyguardNotifications;
     }
 
-    public int getMaxKeyguardNotifications() {
-        return getMaxKeyguardNotifications(false /* recompute */);
+    public int getMaxNotificationsWhileLocked() {
+        return getMaxNotificationsWhileLocked(false /* recompute */);
     }
 
     // TODO: Figure out way to remove these.
@@ -4962,8 +4768,7 @@ public class StatusBar extends SystemUI implements DemoMode,
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
+    protected VisualStabilityManager mVisualStabilityManager;
 
     protected AccessibilityManager mAccessibilityManager;
 
@@ -5340,10 +5145,10 @@ public class StatusBar extends SystemUI implements DemoMode,
         if (mState == StatusBarState.KEYGUARD) {
             // Since the number of notifications is determined based on the height of the view, we
             // need to update them.
-            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
-            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+            int maxBefore = getMaxNotificationsWhileLocked(false /* recompute */);
+            int maxNotifications = getMaxNotificationsWhileLocked(true /* recompute */);
             if (maxBefore != maxNotifications) {
-                updateRowStates();
+                mViewHierarchyManager.updateRowStates();
             }
         }
     }
@@ -5444,76 +5249,8 @@ public class StatusBar extends SystemUI implements DemoMode,
     /**
      * Updates expanded, dimmed and locked states of notification rows.
      */
-    protected void updateRowStates() {
-        final int N = mStackScroller.getChildCount();
-
-        int visibleNotifications = 0;
-        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        int maxNotifications = -1;
-        if (onKeyguard) {
-            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
-        }
-        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
-        Stack<ExpandableNotificationRow> stack = new Stack<>();
-        for (int i = N - 1; i >= 0; i--) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            stack.push((ExpandableNotificationRow) child);
-        }
-        while(!stack.isEmpty()) {
-            ExpandableNotificationRow row = stack.pop();
-            NotificationData.Entry entry = row.getEntry();
-            boolean isChildNotification =
-                    mGroupManager.isChildInGroupWithSummary(entry.notification);
-
-            row.setOnKeyguard(onKeyguard);
-
-            if (!onKeyguard) {
-                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-                // very first notification and if it's not a child of grouped notifications.
-                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
-                        || (visibleNotifications == 0 && !isChildNotification
-                        && !row.isLowPriority()));
-            }
-
-            entry.row.setShowAmbient(isDozing());
-            int userId = entry.notification.getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.notification) && !entry.row.isRemoved();
-            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
-                    .notification);
-            if (suppressedSummary
-                    || (mLockscreenUserManager.isLockscreenPublicMode(userId)
-                            && !mLockscreenUserManager.shouldShowLockscreenNotifications())
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.row.setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.row.getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.row.setVisibility(View.VISIBLE);
-                }
-                if (!isChildNotification && !entry.row.isRemoved()) {
-                    if (wasGone) {
-                        // notify the scroller of a child addition
-                        mStackScroller.generateAddAnimation(entry.row,
-                                !showOnKeyguard /* fromMoreCard */);
-                    }
-                    visibleNotifications++;
-                }
-            }
-            if (row.isSummaryWithChildren()) {
-                List<ExpandableNotificationRow> notificationChildren =
-                        row.getNotificationChildren();
-                int size = notificationChildren.size();
-                for (int i = size - 1; i >= 0; i--) {
-                    stack.push(notificationChildren.get(i));
-                }
-            }
-        }
-        mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
-
+    @Override
+    public void onUpdateRowStates() {
         // The following views will be moved to the end of mStackScroller. This counter represents
         // the offset from the last child. Initialized to 1 for the very last position. It is post-
         // incremented in the following "changeViewPosition" calls so that its value is correct for
index fe39a89..369e7ff 100644 (file)
@@ -80,6 +80,8 @@ import com.android.systemui.statusbar.ExpandableOutlineView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -112,7 +114,8 @@ import java.util.List;
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
+        NotificationListContainer {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -207,7 +210,7 @@ public class NotificationStackScrollLayout extends ViewGroup
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
-    private OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
@@ -447,6 +450,7 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
+    @Override
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -614,7 +618,9 @@ public class NotificationStackScrollLayout extends ViewGroup
         mNoAmbient = noAmbient;
     }
 
-    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
+    @Override
+    public void setChildLocationsChangedListener(
+            NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
@@ -1325,6 +1331,7 @@ public class NotificationStackScrollLayout extends ViewGroup
                 true /* isDismissAll */);
     }
 
+    @Override
     public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
         // If the child is showing the notification menu snap to that
@@ -1333,6 +1340,11 @@ public class NotificationStackScrollLayout extends ViewGroup
     }
 
     @Override
+    public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+        return this;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
@@ -2053,6 +2065,7 @@ public class NotificationStackScrollLayout extends ViewGroup
         return mAmbientState.isPulsing(entry);
     }
 
+    @Override
     public boolean hasPulsingNotifications() {
         return mPulsing != null;
     }
@@ -2610,10 +2623,7 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
-    /**
-     * Called when a notification is removed from the shade. This cleans up the state for a given
-     * view.
-     */
+    @Override
     public void cleanUpViewState(View child) {
         if (child == mTranslatingParentView) {
             mTranslatingParentView = null;
@@ -2922,10 +2932,12 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
+    @Override
     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
 
+    @Override
     public void notifyGroupChildAdded(View row) {
         onViewAddedInternal(row);
     }
@@ -2963,12 +2975,8 @@ public class NotificationStackScrollLayout extends ViewGroup
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
-    /**
-     * Generate an animation for an added child view.
-     *
-     * @param child The view to be added.
-     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
-     */
+
+    @Override
     public void generateAddAnimation(View child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
             // Generate Animations
@@ -2984,12 +2992,7 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
-    /**
-     * Change the position of child to a new location
-     *
-     * @param child the view to change the position for
-     * @param newIndex the new index
-     */
+    @Override
     public void changeViewPosition(View child, int newIndex) {
         int currentIndex = indexOfChild(child);
         if (child != null && child.getParent() == this && currentIndex != newIndex) {
@@ -3705,7 +3708,7 @@ public class NotificationStackScrollLayout extends ViewGroup
     private void applyCurrentState() {
         mCurrentStackScrollState.apply();
         if (mListener != null) {
-            mListener.onChildLocationsChanged(this);
+            mListener.onChildLocationsChanged();
         }
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
@@ -4189,6 +4192,26 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
+    @Override
+    public int getContainerChildCount() {
+        return getChildCount();
+    }
+
+    @Override
+    public View getContainerChildAt(int i) {
+        return getChildAt(i);
+    }
+
+    @Override
+    public void removeContainerView(View v) {
+        removeView(v);
+    }
+
+    @Override
+    public void addContainerView(View v) {
+        addView(v);
+    }
+
     public void runAfterAnimationFinished(Runnable runnable) {
         mAnimationFinishedRunnables.add(runnable);
     }
@@ -4445,13 +4468,6 @@ public class NotificationStackScrollLayout extends ViewGroup
     }
 
     /**
-     * A listener that is notified when some child locations might have changed.
-     */
-    public interface OnChildLocationsChangedListener {
-        void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
-    }
-
-    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     public interface OnEmptySpaceClickListener {
@@ -4706,6 +4722,7 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
+    @Override
     public void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
index dcd0c83..1354717 100644 (file)
@@ -41,7 +41,7 @@ import android.service.notification.StatusBarNotification;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -53,7 +53,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -83,9 +82,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
     @Mock private NotificationListener mNotificationListener;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private NotificationStackScrollLayout mStackScroller;
-    @Mock private NotificationEntryManager.Callback mCallback;
     @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private NotificationEntryManager.Callback mCallback;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationListenerService.RankingMap mRankingMap;
     @Mock private RemoteInputController mRemoteInputController;
@@ -110,11 +109,12 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
                 NotificationListener notificationListener,
                 MetricsLogger metricsLogger,
                 DeviceProvisionedController deviceProvisionedController,
+                VisualStabilityManager visualStabilityManager,
                 UiOffloadThread uiOffloadThread, Context context,
                 IStatusBarService barService) {
             super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager,
                     mediaManager, foregroundServiceController, notificationListener, metricsLogger,
-                    deviceProvisionedController, uiOffloadThread, context);
+                    deviceProvisionedController, visualStabilityManager, uiOffloadThread, context);
             mBarService = barService;
             mCountDownLatch = new CountDownLatch(1);
             mUseHeadsUp = true;
@@ -143,9 +143,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
         when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
         when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
-        // Necessary for layout inflation.
-        when(mStackScroller.generateLayoutParams(any())).thenReturn(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        when(mListContainer.getViewParentForNotification(any())).thenReturn(
+                new FrameLayout(mContext));
 
         Notification.Builder n = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
@@ -159,10 +158,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
         mEntryManager = new TestableNotificationEntryManager(mLockscreenUserManager,
                 mGroupManager, mGutsManager, mRemoteInputManager, mMediaManager,
                 mForegroundServiceController, mNotificationListener, mMetricsLogger,
-                mDeviceProvisionedController, mDependency.get(UiOffloadThread.class), mContext,
+                mDeviceProvisionedController, mVisualStabilityManager,
+                mDependency.get(UiOffloadThread.class), mContext,
                 mBarService);
-        mEntryManager.setUpWithPresenter(mPresenter, mStackScroller, mCallback,
-                mVisualStabilityManager, mHeadsUpManager);
+        mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
     }
 
     @Test
@@ -244,7 +243,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
         verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
         verify(mRemoteInputManager).onRemoveNotification(mEntry);
         verify(mForegroundServiceController).removeNotification(mSbn);
-        verify(mStackScroller).cleanUpViewState(mRow);
+        verify(mListContainer).cleanUpViewState(mRow);
         verify(mPresenter).updateNotificationViews();
         verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
         verify(mRow).setRemoved();
index b0396ef..4908870 100644 (file)
@@ -36,7 +36,6 @@ import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import com.google.android.collect.Lists;
 
@@ -57,7 +56,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotificationListener mListener;
-    @Mock private NotificationStackScrollLayout mStackScroller;
+    @Mock private NotificationListContainer mListContainer;
     @Mock private IStatusBarService mBarService;
     @Mock private NotificationData mNotificationData;
     @Mock private ExpandableNotificationRow mRow;
@@ -80,14 +79,14 @@ public class NotificationLoggerTest extends SysuiTestCase {
 
         mLogger = new TestableNotificationLogger(mListener, mDependency.get(UiOffloadThread.class),
                 mBarService);
-        mLogger.setUpWithPresenter(mPresenter, mStackScroller);
+        mLogger.setUpWithPresenter(mPresenter, mListContainer);
     }
 
     @Test
     public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
-        when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
 
@@ -99,7 +98,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
 
         // |mEntry| won't change visibility, so it shouldn't be reported again:
         Mockito.reset(mBarService);
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
 
@@ -109,9 +108,9 @@ public class NotificationLoggerTest extends SysuiTestCase {
     @Test
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
-        when(mStackScroller.isInVisibleLocation(any())).thenReturn(true);
+        when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
         when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry));
-        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(mStackScroller);
+        mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         waitForIdleSync(mLogger.getHandlerForTest());
         waitForUiOffloadThread();
         Mockito.reset(mBarService);
@@ -135,7 +134,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
             mHandler = new Handler(Looper.getMainLooper());
         }
 
-        public NotificationStackScrollLayout.OnChildLocationsChangedListener
+        public OnChildLocationsChangedListener
                 getChildLocationsChangedListenerForTest() {
             return mNotificationLocationsChangedListener;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
new file mode 100644 (file)
index 0000000..7ea3ec4
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 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 static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private NotificationGroupManager mGroupManager;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private NotificationData mNotificationData;
+    @Spy private FakeListContainer mListContainer = new FakeListContainer();
+
+    private NotificationViewHierarchyManager mViewHierarchyManager;
+    private NotificationTestHelper mHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHelper = new NotificationTestHelper(mContext);
+
+        when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+        when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
+
+        mViewHierarchyManager = new NotificationViewHierarchyManager(mLockscreenUserManager,
+                mGroupManager, mVisualStabilityManager, mContext);
+        mViewHierarchyManager.setUpWithPresenter(mPresenter, mEntryManager, mListContainer);
+    }
+
+    private NotificationData.Entry createEntry() throws Exception {
+        ExpandableNotificationRow row = mHelper.createRow();
+        NotificationData.Entry entry = new NotificationData.Entry(row.getStatusBarNotification());
+        entry.row = row;
+        return entry;
+    }
+
+    @Test
+    public void testNotificationsBecomingBundled() throws Exception {
+        // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
+        // the summary.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+
+        // Set up the prior state to look like three top level notifications.
+        mListContainer.addContainerView(entry0.row);
+        mListContainer.addContainerView(entry1.row);
+        mListContainer.addContainerView(entry2.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true);
+        when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0.row);
+        when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0.row);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildAdded(entry1.row);
+        verify(mListContainer).notifyGroupChildAdded(entry2.row);
+        assertTrue(Lists.newArrayList(entry0.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingUnbundled() throws Exception {
+        // Tests a bundled notification becoming three top level notifications.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        NotificationData.Entry entry2 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+        entry0.row.addChildNotification(entry2.row);
+
+        // Set up the prior state to look like one top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1, entry2));
+
+        // Set up group manager to report that they should not be bundled now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry2.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row, entry2.row).equals(mListContainer.mRows));
+    }
+
+    @Test
+    public void testNotificationsBecomingSuppressed() throws Exception {
+        // Tests two top level notifications becoming a suppressed summary and a child.
+        NotificationData.Entry entry0 = createEntry();
+        NotificationData.Entry entry1 = createEntry();
+        entry0.row.addChildNotification(entry1.row);
+
+        // Set up the prior state to look like a top level notification.
+        mListContainer.addContainerView(entry0.row);
+        when(mNotificationData.getActiveNotifications()).thenReturn(
+                Lists.newArrayList(entry0, entry1));
+
+        // Set up group manager to report a suppressed summary now.
+        when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
+        when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
+        when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true);
+
+        // Run updateNotifications - the view hierarchy should be reorganized.
+        mViewHierarchyManager.updateNotificationViews();
+
+        verify(mListContainer).notifyGroupChildRemoved(
+                entry1.row, entry0.row.getChildrenContainer());
+        assertTrue(Lists.newArrayList(entry0.row, entry1.row).equals(mListContainer.mRows));
+        assertEquals(View.GONE, entry0.row.getVisibility());
+        assertEquals(View.VISIBLE, entry1.row.getVisibility());
+    }
+
+    private class FakeListContainer implements NotificationListContainer {
+        final LinearLayout mLayout = new LinearLayout(mContext);
+        final List<View> mRows = Lists.newArrayList();
+
+        @Override
+        public void setChildTransferInProgress(boolean childTransferInProgress) {}
+
+        @Override
+        public void changeViewPosition(View child, int newIndex) {
+            mRows.remove(child);
+            mRows.add(newIndex, child);
+        }
+
+        @Override
+        public void notifyGroupChildAdded(View row) {}
+
+        @Override
+        public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {}
+
+        @Override
+        public void generateAddAnimation(View child, boolean fromMoreCard) {}
+
+        @Override
+        public void generateChildOrderChangedEvent() {}
+
+        @Override
+        public int getContainerChildCount() {
+            return mRows.size();
+        }
+
+        @Override
+        public View getContainerChildAt(int i) {
+            return mRows.get(i);
+        }
+
+        @Override
+        public void removeContainerView(View v) {
+            mLayout.removeView(v);
+            mRows.remove(v);
+        }
+
+        @Override
+        public void addContainerView(View v) {
+            mLayout.addView(v);
+            mRows.add(v);
+        }
+
+        @Override
+        public void setMaxDisplayedNotifications(int maxNotifications) {}
+
+        @Override
+        public void snapViewIfNeeded(ExpandableNotificationRow row) {}
+
+        @Override
+        public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+            return null;
+        }
+
+        @Override
+        public void onHeightChanged(ExpandableView view, boolean animate) {}
+
+        @Override
+        public void resetExposedMenuView(boolean animate, boolean force) {}
+
+        @Override
+        public NotificationSwipeActionHelper getSwipeActionHelper() {
+            return null;
+        }
+
+        @Override
+        public void cleanUpViewState(View view) {}
+
+        @Override
+        public boolean isInVisibleLocation(ExpandableNotificationRow row) {
+            return true;
+        }
+
+        @Override
+        public void setChildLocationsChangedListener(
+                NotificationLogger.OnChildLocationsChangedListener listener) {}
+
+        @Override
+        public boolean hasPulsingNotifications() {
+            return false;
+        }
+    }
+}
index db83655..c10de61 100644 (file)
@@ -76,12 +76,14 @@ import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationEntryManager;
 import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListContainer;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLogger;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -103,25 +105,27 @@ import java.util.ArrayList;
 @RunWithLooper
 public class StatusBarTest extends SysuiTestCase {
 
-    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    UnlockMethodCache mUnlockMethodCache;
-    KeyguardIndicationController mKeyguardIndicationController;
-    NotificationStackScrollLayout mStackScroller;
-    TestableStatusBar mStatusBar;
-    FakeMetricsLogger mMetricsLogger;
-    HeadsUpManager mHeadsUpManager;
-    NotificationData mNotificationData;
-    PowerManager mPowerManager;
-    SystemServicesProxy mSystemServicesProxy;
-    NotificationPanelView mNotificationPanelView;
-    ScrimController mScrimController;
-    IStatusBarService mBarService;
-    NotificationListener mNotificationListener;
-    NotificationLogger mNotificationLogger;
-    ArrayList<Entry> mNotificationList;
-    FingerprintUnlockController mFingerprintUnlockController;
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private UnlockMethodCache mUnlockMethodCache;
+    private KeyguardIndicationController mKeyguardIndicationController;
+    private NotificationStackScrollLayout mStackScroller;
+    private TestableStatusBar mStatusBar;
+    private FakeMetricsLogger mMetricsLogger;
+    private HeadsUpManager mHeadsUpManager;
+    private NotificationData mNotificationData;
+    private PowerManager mPowerManager;
+    private SystemServicesProxy mSystemServicesProxy;
+    private NotificationPanelView mNotificationPanelView;
+    private ScrimController mScrimController;
+    private IStatusBarService mBarService;
+    private NotificationListener mNotificationListener;
+    private NotificationLogger mNotificationLogger;
+    private ArrayList<Entry> mNotificationList;
+    private FingerprintUnlockController mFingerprintUnlockController;
     private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private TestableNotificationEntryManager mEntryManager;
+    private NotificationViewHierarchyManager mViewHierarchyManager;
+    private VisualStabilityManager mVisualStabilityManager;
 
     @Before
     public void setup() throws Exception {
@@ -133,7 +137,10 @@ public class StatusBarTest extends SysuiTestCase {
         mDependency.injectMockDependency(NotificationRemoteInputManager.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         mDependency.injectMockDependency(ForegroundServiceController.class);
-        mDependency.injectMockDependency(NotificationListener.class);
+        mNotificationListener = mDependency.injectMockDependency(NotificationListener.class);
+        mViewHierarchyManager = mDependency.injectMockDependency(
+                NotificationViewHierarchyManager.class);
+        mVisualStabilityManager = mDependency.injectMockDependency(VisualStabilityManager.class);
         mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
         CommandQueue commandQueue = mock(CommandQueue.class);
         when(commandQueue.asBinder()).thenReturn(new Binder());
@@ -161,7 +168,6 @@ public class StatusBarTest extends SysuiTestCase {
                 new Handler(handlerThread.getLooper()));
         when(powerManagerService.isInteractive()).thenReturn(true);
         mBarService = mock(IStatusBarService.class);
-        mNotificationListener = mock(NotificationListener.class);
         mNotificationLogger = new NotificationLogger(mNotificationListener, mDependency.get(
                 UiOffloadThread.class));
 
@@ -175,7 +181,8 @@ public class StatusBarTest extends SysuiTestCase {
         mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
                 mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
                 mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
-                mNotificationLogger, mEntryManager, mScrimController, mFingerprintUnlockController);
+                mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
+                mEntryManager, mScrimController, mFingerprintUnlockController);
         mStatusBar.mContext = mContext;
         mStatusBar.mComponents = mContext.getComponents();
         doAnswer(invocation -> {
@@ -190,8 +197,8 @@ public class StatusBarTest extends SysuiTestCase {
             return null;
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
-        mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar,
-                mock(VisualStabilityManager.class), mHeadsUpManager, mNotificationData);
+        mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, mHeadsUpManager,
+                mNotificationData);
         mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller);
 
         when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -590,6 +597,8 @@ public class StatusBarTest extends SysuiTestCase {
                 PowerManager pm, NotificationPanelView panelView,
                 IStatusBarService barService, NotificationListener notificationListener,
                 NotificationLogger notificationLogger,
+                VisualStabilityManager visualStabilityManager,
+                NotificationViewHierarchyManager viewHierarchyManager,
                 TestableNotificationEntryManager entryManager, ScrimController scrimController,
                 FingerprintUnlockController fingerprintUnlockController) {
             mStatusBarKeyguardViewManager = man;
@@ -603,6 +612,8 @@ public class StatusBarTest extends SysuiTestCase {
             mNotificationListener = notificationListener;
             mNotificationLogger = notificationLogger;
             mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+            mVisualStabilityManager = visualStabilityManager;
+            mViewHierarchyManager = viewHierarchyManager;
             mEntryManager = entryManager;
             mScrimController = scrimController;
             mFingerprintUnlockController = fingerprintUnlockController;
@@ -644,6 +655,7 @@ public class StatusBarTest extends SysuiTestCase {
                     mDependency.get(NotificationListener.class),
                     metricsLogger,
                     mDependency.get(DeviceProvisionedController.class),
+                    mDependency.get(VisualStabilityManager.class),
                     mDependency.get(UiOffloadThread.class),
                     context);
             mSystemServicesProxy = systemServicesProxy;
@@ -651,13 +663,11 @@ public class StatusBarTest extends SysuiTestCase {
         }
 
         public void setUpForTest(NotificationPresenter presenter,
-                NotificationStackScrollLayout stackScroller,
+                NotificationListContainer listContainer,
                 Callback callback,
-                VisualStabilityManager visualStabilityManager,
                 HeadsUpManager headsUpManager,
                 NotificationData notificationData) {
-            super.setUpWithPresenter(presenter, stackScroller, callback, visualStabilityManager,
-                    headsUpManager);
+            super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
             mNotificationData = notificationData;
             mUseHeadsUp = true;
         }