diff options
| author | 2017-11-27 13:27:46 +0900 | |
|---|---|---|
| committer | 2017-12-26 16:43:53 +0900 | |
| commit | 2b4c3a08fcfcb60c527a1a372318e5ef4ce2d49c (patch) | |
| tree | 17caad9d703ff3010a3171b48f4a6334f00a0f67 | |
| parent | a6d8cf294dfb587f130fdecc5e6897e75de7bf45 (diff) | |
Split NotificationViewHierarchyManager out of StatusBar.
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, 976 insertions, 402 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index c32e08946621..47148a44d66c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -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)); } } diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index 37172b63caae..20418c3f59ad 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -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); } /** diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java index 24f8b97d539d..55965e7f1f8e 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java @@ -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)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java index b60d2ac8ebf4..90b46c8d2cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java index d6a8af5cedd6..0cecadfab9be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java @@ -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 index 000000000000..43be44deedc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java @@ -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(); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java index e958f3fe54d5..9ff7c18a570a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java @@ -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(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index c1dd958506f6..ec1a8bae1f28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -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 index 000000000000..b73b9b6eb4cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -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(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 61dd22fc4571..f0bd1f94ec3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -455,7 +455,7 @@ public class NotificationPanelView extends PanelView implements mTopPaddingAdjustment = 0; } else { mClockPositionAlgorithm.setup( - mStatusBar.getMaxKeyguardNotifications(), + mStatusBar.getMaxNotificationsWhileLocked(), getMaxPanelHeight(), getExpandedHeight(), mNotificationStackScroller.getNotGoneChildCount(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 04fe7f206021..72a52d84854d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index fe39a894a094..369e7ffa991c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -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); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java index dcd0c8393756..1354717793cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java @@ -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(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java index b0396ef5131e..4908870b1db8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java @@ -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 index 000000000000..7ea3ec4f5f06 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -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; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index db83655058c1..c10de6150e30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -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; } |