diff options
19 files changed, 1756 insertions, 1041 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 3177c03d1afa..c32e08946621 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -23,16 +23,19 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; 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.NotificationLogger; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.DozeParameters; @@ -46,6 +49,7 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.util.function.Consumer; @@ -119,6 +123,7 @@ public class SystemUIFactory { providers.put(NotificationLockscreenUserManager.class, () -> new NotificationLockscreenUserManager(context)); providers.put(NotificationGroupManager.class, NotificationGroupManager::new); + providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context)); providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager( Dependency.get(NotificationLockscreenUserManager.class), context)); providers.put(NotificationRemoteInputManager.class, @@ -129,5 +134,20 @@ public class SystemUIFactory { providers.put(NotificationLogger.class, () -> new NotificationLogger( Dependency.get(NotificationListener.class), Dependency.get(UiOffloadThread.class))); + providers.put(NotificationEntryManager.class, () -> + new NotificationEntryManager( + Dependency.get(NotificationLockscreenUserManager.class), + Dependency.get(NotificationGroupManager.class), + Dependency.get(NotificationGutsManager.class), + Dependency.get(NotificationRemoteInputManager.class), + Dependency.get(NotificationMediaManager.class), + Dependency.get(ForegroundServiceController.class), + Dependency.get(NotificationListener.class), + Dependency.get(MetricsLogger.class), + Dependency.get(DeviceProvisionedController.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 new file mode 100644 index 000000000000..37172b63caae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -0,0 +1,76 @@ +/* + * 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.car; + +import android.content.Context; +import android.service.notification.StatusBarNotification; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; +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.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; + +public class CarNotificationEntryManager extends NotificationEntryManager { + public CarNotificationEntryManager( + NotificationLockscreenUserManager lockscreenUserManager, + NotificationGroupManager groupManager, + NotificationGutsManager gutsManager, + NotificationRemoteInputManager remoteInputManager, + NotificationMediaManager mediaManager, + ForegroundServiceController foregroundServiceController, + NotificationListener notificationListener, + MetricsLogger metricsLogger, + DeviceProvisionedController deviceProvisionedController, + UiOffloadThread uiOffloadThread, Context context) { + super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager, mediaManager, + foregroundServiceController, notificationListener, metricsLogger, + deviceProvisionedController, uiOffloadThread, context); + } + + /** + * Returns the + * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will + * be triggered when a notification card is long-pressed. + */ + @Override + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + // For the automative use case, we do not want to the user to be able to interact with + // a notification other than a regular click. As a result, just return null for the + // long click listener. + return null; + } + + @Override + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + // Because space is usually constrained in the auto use-case, there should not be a + // pinned notification when the shade has been expanded. Ensure this by not pinning any + // notification if the shade is already opened. + if (!mPresenter.isPresenterFullyCollapsed()) { + return false; + } + + return super.shouldPeek(entry, sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java index 5a19e7dc60ed..24f8b97d539d 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java @@ -18,9 +18,21 @@ package com.android.systemui.car; import android.content.Context; import android.util.ArrayMap; +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.Dependency; import com.android.systemui.Dependency.DependencyProvider; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.SystemUIFactory; +import com.android.systemui.UiOffloadThread; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.NotificationGutsManager; +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.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.volume.car.CarVolumeDialogController; /** @@ -32,5 +44,17 @@ public class CarSystemUIFactory extends SystemUIFactory { Context context) { super.injectDependencies(providers, context); providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context)); + providers.put(NotificationEntryManager.class, () -> new CarNotificationEntryManager( + Dependency.get(NotificationLockscreenUserManager.class), + Dependency.get(NotificationGroupManager.class), + Dependency.get(NotificationGutsManager.class), + Dependency.get(NotificationRemoteInputManager.class), + Dependency.get(NotificationMediaManager.class), + Dependency.get(ForegroundServiceController.class), + Dependency.get(NotificationListener.class), + Dependency.get(MetricsLogger.class), + Dependency.get(DeviceProvisionedController.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 new file mode 100644 index 000000000000..b60d2ac8ebf4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java @@ -0,0 +1,966 @@ +/* + * 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 com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.statusbar.NotificationRemoteInputManager + .FORCE_REMOTE_INPUT_HISTORY; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.os.Build; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.util.EventLog; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.DejankUtils; +import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; +import com.android.systemui.EventLogTags; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.NotificationInflater; +import com.android.systemui.statusbar.notification.RowInflaterTask; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +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; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. + * It also handles tasks such as their inflation and their interaction with other + * Notification.*Manager objects. + */ +public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, + ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler { + private static final String TAG = "NotificationEntryManager"; + protected static final boolean DEBUG = false; + protected static final boolean ENABLE_HEADS_UP = true; + protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; + protected final NotificationMessagingUtil mMessagingUtil; + protected final Context mContext; + protected final NotificationLockscreenUserManager mLockscreenUserManager; + protected final NotificationGroupManager mGroupManager; + protected final NotificationGutsManager mGutsManager; + protected final NotificationRemoteInputManager mRemoteInputManager; + protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); + protected final NotificationMediaManager mMediaManager; + protected final MetricsLogger mMetricsLogger; + protected final DeviceProvisionedController mDeviceProvisionedController; + protected final UiOffloadThread mUiOffloadThread; + protected final ForegroundServiceController mForegroundServiceController; + protected final NotificationListener mNotificationListener; + protected final NotificationClicker mNotificationClicker = new NotificationClicker(); + protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch = + new ArraySet<>(); + + protected IStatusBarService mBarService; + protected NotificationPresenter mPresenter; + protected Callback mCallback; + protected NotificationStackScrollLayout mStackScroller; + protected PowerManager mPowerManager; + protected SystemServicesProxy mSystemServicesProxy; + protected NotificationListenerService.RankingMap mLatestRankingMap; + protected HeadsUpManager mHeadsUpManager; + protected NotificationData mNotificationData; + protected ContentObserver mHeadsUpObserver; + protected boolean mUseHeadsUp = false; + protected boolean mDisableNotificationAlerts; + protected VisualStabilityManager mVisualStabilityManager; + + private final class NotificationClicker implements View.OnClickListener { + + @Override + public void onClick(final View v) { + if (!(v instanceof ExpandableNotificationRow)) { + Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); + return; + } + + mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v); + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final StatusBarNotification sbn = row.getStatusBarNotification(); + if (sbn == null) { + Log.e(TAG, "NotificationClicker called on an unclickable notification,"); + return; + } + + // Check if the notification is displaying the menu, if so slide notification back + if (row.getProvider() != null && row.getProvider().isMenuVisible()) { + row.animateTranslateNotification(0); + return; + } + + // Mark notification for one frame. + row.setJustClicked(true); + DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); + + mCallback.onNotificationClicked(sbn, row); + } + + public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { + row.setOnClickListener(this); + } else { + row.setOnClickListener(null); + } + } + } + + private final DeviceProvisionedController.DeviceProvisionedListener + mDeviceProvisionedListener = + new DeviceProvisionedController.DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + updateNotifications(); + } + }; + + public NotificationListenerService.RankingMap getLatestRankingMap() { + return mLatestRankingMap; + } + + public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) { + mLatestRankingMap = latestRankingMap; + } + + public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { + mDisableNotificationAlerts = disableNotificationAlerts; + mHeadsUpObserver.onChange(true); + } + + public void destroy() { + mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + } + + public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, getLatestRankingMap()); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + setLatestRankingMap(null); + } + } else { + updateNotificationRanking(null); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationEntryManager state:"); + pw.print(" mPendingNotifications="); + if (mPendingNotifications.size() == 0) { + pw.println("null"); + } else { + for (NotificationData.Entry entry : mPendingNotifications.values()) { + pw.println(entry.notification); + } + } + pw.print(" mUseHeadsUp="); + pw.println(mUseHeadsUp); + } + + public NotificationEntryManager(NotificationLockscreenUserManager lockscreenUserManager, + NotificationGroupManager groupManager, + NotificationGutsManager gutsManager, + NotificationRemoteInputManager remoteInputManager, + NotificationMediaManager mediaManager, + ForegroundServiceController foregroundServiceController, + NotificationListener notificationListener, + MetricsLogger metricsLogger, + DeviceProvisionedController deviceProvisionedController, + UiOffloadThread uiOffloadThread, Context context) { + mLockscreenUserManager = lockscreenUserManager; + mGroupManager = groupManager; + mGutsManager = gutsManager; + mRemoteInputManager = remoteInputManager; + mMediaManager = mediaManager; + mForegroundServiceController = foregroundServiceController; + mNotificationListener = notificationListener; + mMetricsLogger = metricsLogger; + mDeviceProvisionedController = deviceProvisionedController; + mUiOffloadThread = uiOffloadThread; + mContext = context; + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mMessagingUtil = new NotificationMessagingUtil(context); + mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); + } + + // TODO: Remove dependency on NotificationStackScrollLayout + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationStackScrollLayout stackScroller, + Callback callback, + VisualStabilityManager visualStabilityManager, + HeadsUpManager headsUpManager) { + mPresenter = presenter; + mCallback = callback; + mStackScroller = stackScroller; + mVisualStabilityManager = visualStabilityManager; + mNotificationData = new NotificationData(presenter); + mHeadsUpManager = headsUpManager; + mNotificationData.setHeadsUpManager(mHeadsUpManager); + + mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) { + @Override + public void onChange(boolean selfChange) { + boolean wasUsing = mUseHeadsUp; + mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts + && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF); + Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); + if (wasUsing != mUseHeadsUp) { + if (!mUseHeadsUp) { + Log.d(TAG, + "dismissing any existing heads up notification on disable event"); + mHeadsUpManager.releaseAllImmediately(); + } + } + } + }; + + if (ENABLE_HEADS_UP) { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), + true, + mHeadsUpObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, + mHeadsUpObserver); + } + + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + + mHeadsUpObserver.onChange(true); // set up + } + + public NotificationData getNotificationData() { + return mNotificationData; + } + + public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + return mGutsManager::openGuts; + } + + @Override + public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationExpansionChanged(key, userAction, expanded); + } catch (RemoteException e) { + // Ignore. + } + }); + } + + private boolean shouldSuppressFullScreenIntent(String key) { + if (mPresenter.isDeviceInVrMode()) { + return true; + } + + if (mPowerManager.isInteractive()) { + return mNotificationData.shouldSuppressScreenOn(key); + } else { + return mNotificationData.shouldSuppressScreenOff(key); + } + } + + private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { + PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, + entry.notification.getUser().getIdentifier()); + + final StatusBarNotification sbn = entry.notification; + if (entry.row != null) { + entry.reset(); + updateNotification(entry, pmUser, sbn, entry.row); + } else { + new RowInflaterTask().inflate(mContext, parent, entry, + row -> { + bindRow(entry, pmUser, sbn, row); + updateNotification(entry, pmUser, sbn, row); + }); + } + } + + private void bindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setExpansionLogger(this, entry.notification.getKey()); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(this); + row.setLongPressListener(getNotificationLongClicker()); + mRemoteInputManager.bindRow(row); + + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + final String pkg = sbn.getPackageName(); + String appname = pkg; + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + appname = String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + row.setAppName(appname); + row.setOnDismissRunnable(() -> + performRemoveNotification(row.getStatusBarNotification())); + row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mCallback.onBindRow(entry, pmUser, sbn, row); + } + + public void performRemoveNotification(StatusBarNotification n) { + NotificationData.Entry entry = mNotificationData.get(n.getKey()); + mRemoteInputManager.onPerformRemoveNotification(n, entry); + final String pkg = n.getPackageName(); + final String tag = n.getTag(); + final int id = n.getId(); + final int userId = n.getUserId(); + try { + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + if (isHeadsUp(n.getKey())) { + dismissalSurface = NotificationStats.DISMISSAL_PEEK; + } else if (mStackScroller.hasPulsingNotifications()) { + dismissalSurface = NotificationStats.DISMISSAL_AOD; + } + mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); + removeNotification(n.getKey(), null); + + } catch (RemoteException ex) { + // system process is dead if we're here. + } + + mCallback.onPerformRemoveNotification(n); + } + + /** + * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService + * about the failure. + * + * WARNING: this will call back into us. Don't hold any locks. + */ + void handleNotificationError(StatusBarNotification n, String message) { + removeNotification(n.getKey(), null); + try { + mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), + n.getInitialPid(), message, n.getUserId()); + } catch (RemoteException ex) { + // The end is nigh. + } + } + + private void abortExistingInflation(String key) { + if (mPendingNotifications.containsKey(key)) { + NotificationData.Entry entry = mPendingNotifications.get(key); + entry.abortTask(); + mPendingNotifications.remove(key); + } + NotificationData.Entry addedEntry = mNotificationData.get(key); + if (addedEntry != null) { + addedEntry.abortTask(); + } + } + + @Override + public void handleInflationException(StatusBarNotification notification, Exception e) { + handleNotificationError(notification, e.getMessage()); + } + + private void addEntry(NotificationData.Entry shadeEntry) { + boolean isHeadsUped = shouldPeek(shadeEntry); + if (isHeadsUped) { + mHeadsUpManager.showNotification(shadeEntry); + // Mark as seen immediately + setNotificationShown(shadeEntry.notification); + } + addNotificationViews(shadeEntry); + mCallback.onNotificationAdded(shadeEntry); + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + mPendingNotifications.remove(entry.key); + // If there was an async task started after the removal, we don't want to add it back to + // the list, otherwise we might get leaks. + boolean isNew = mNotificationData.get(entry.key) == null; + if (isNew && !entry.row.isRemoved()) { + addEntry(entry); + } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { + mVisualStabilityManager.onLowPriorityUpdated(entry); + mPresenter.updateNotificationViews(); + } + entry.row.setLowPriorityStateUpdated(false); + } + + @Override + public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { + boolean deferRemoval = false; + abortExistingInflation(key); + if (mHeadsUpManager.isHeadsUp(key)) { + // A cancel() in response to a remote input shouldn't be delayed, as it makes the + // sending look longer than it takes. + // Also we should not defer the removal if reordering isn't allowed since otherwise + // some notifications can't disappear before the panel is closed. + boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) + && !FORCE_REMOTE_INPUT_HISTORY + || !mVisualStabilityManager.isReorderingAllowed(); + deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); + } + mMediaManager.onNotificationRemoved(key); + + NotificationData.Entry entry = mNotificationData.get(key); + if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key) + && entry.row != null && !entry.row.isDismissed()) { + StatusBarNotification sbn = entry.notification; + + Notification.Builder b = Notification.Builder + .recoverBuilder(mContext, sbn.getNotification().clone()); + CharSequence[] oldHistory = sbn.getNotification().extras + .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); + CharSequence[] newHistory; + if (oldHistory == null) { + newHistory = new CharSequence[1]; + } else { + newHistory = new CharSequence[oldHistory.length + 1]; + System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); + } + newHistory[0] = String.valueOf(entry.remoteInputText); + b.setRemoteInputHistory(newHistory); + + Notification newNotification = b.build(); + + // Undo any compatibility view inflation + newNotification.contentView = sbn.getNotification().contentView; + newNotification.bigContentView = sbn.getNotification().bigContentView; + newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; + + StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + boolean updated = false; + try { + updateNotificationInternal(newSbn, null); + updated = true; + } catch (InflationException e) { + deferRemoval = false; + } + if (updated) { + Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); + mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); + return; + } + } + if (deferRemoval) { + mLatestRankingMap = ranking; + mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + return; + } + + if (mRemoteInputManager.onRemoveNotification(entry)) { + mLatestRankingMap = ranking; + return; + } + + if (entry != null && mGutsManager.getExposedGuts() != null + && mGutsManager.getExposedGuts() == entry.row.getGuts() + && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { + Log.w(TAG, "Keeping notification because it's showing guts. " + key); + mLatestRankingMap = ranking; + mGutsManager.setKeyToRemoveOnGutsClosed(key); + return; + } + + if (entry != null) { + mForegroundServiceController.removeNotification(entry.notification); + } + + if (entry != null && entry.row != null) { + entry.row.setRemoved(); + mStackScroller.cleanUpViewState(entry.row); + } + // Let's remove the children if this was a summary + handleGroupSummaryRemoved(key); + StatusBarNotification old = removeNotificationViews(key, ranking); + + mCallback.onNotificationRemoved(key, old); + } + + private StatusBarNotification removeNotificationViews(String key, + NotificationListenerService.RankingMap ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); + if (entry == null) { + Log.w(TAG, "removeNotification for unknown key: " + key); + return null; + } + updateNotifications(); + Dependency.get(LeakDetector.class).trackGarbage(entry); + return entry.notification; + } + + /** + * Ensures that the group children are cancelled immediately when the group summary is cancelled + * instead of waiting for the notification manager to send all cancels. Otherwise this could + * lead to flickers. + * + * This also ensures that the animation looks nice and only consists of a single disappear + * animation instead of multiple. + * @param key the key of the notification was removed + * + */ + private void handleGroupSummaryRemoved(String key) { + NotificationData.Entry entry = mNotificationData.get(key); + if (entry != null && entry.row != null + && entry.row.isSummaryWithChildren()) { + if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { + // We don't want to remove children for autobundled notifications as they are not + // always cancelled. We only remove them if they were dismissed by the user. + return; + } + List<ExpandableNotificationRow> notificationChildren = + entry.row.getNotificationChildren(); + for (int i = 0; i < notificationChildren.size(); i++) { + ExpandableNotificationRow row = notificationChildren.get(i); + if ((row.getStatusBarNotification().getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + // the child is a foreground service notification which we can't remove! + continue; + } + row.setKeepInParent(true); + // we need to set this state earlier as otherwise we might generate some weird + // animations + row.setRemoved(); + } + } + } + + public void updateNotificationsOnDensityOrFontScaleChanged() { + ArrayList<NotificationData.Entry> activeNotifications = + mNotificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + NotificationData.Entry entry = activeNotifications.get(i); + boolean exposedGuts = mGutsManager.getExposedGuts() != null + && entry.row.getGuts() == mGutsManager.getExposedGuts(); + entry.row.onDensityOrFontScaleChanged(); + if (exposedGuts) { + mGutsManager.setExposedGuts(entry.row.getGuts()); + mGutsManager.bindGuts(entry.row); + } + } + } + + private void updateNotification(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); + boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); + boolean isUpdate = mNotificationData.get(entry.key) != null; + boolean wasLowPriority = row.isLowPriority(); + row.setIsLowPriority(isLowPriority); + row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); + // bind the click event to the content area + mNotificationClicker.register(row, sbn); + + // Extract target SDK version. + try { + ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); + entry.targetSdk = info.targetSdkVersion; + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); + } + row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD + && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.autoRedacted = entry.notification.getNotification().publicVersion == null; + + entry.row = row; + entry.row.setOnActivatedListener(mPresenter); + + boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, + mNotificationData.getImportance(sbn.getKey())); + boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + && !mPresenter.isPresenterFullyCollapsed(); + row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.updateNotification(entry); + } + + + protected void addNotificationViews(NotificationData.Entry entry) { + if (entry == null) { + return; + } + // Add the expanded view and icon. + mNotificationData.add(entry); + updateNotifications(); + } + + protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) + throws InflationException { + if (DEBUG) { + Log.d(TAG, "createNotificationViews(notification=" + sbn); + } + NotificationData.Entry entry = new NotificationData.Entry(sbn); + Dependency.get(LeakDetector.class).trackInstance(entry); + entry.createIcons(mContext, sbn); + // Construct the expanded view. + inflateViews(entry, mStackScroller); + return entry; + } + + private void addNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + String key = notification.getKey(); + if (DEBUG) Log.d(TAG, "addNotification key=" + key); + + mNotificationData.updateRanking(ranking); + NotificationData.Entry shadeEntry = createNotificationViews(notification); + boolean isHeadsUped = shouldPeek(shadeEntry); + if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { + if (shouldSuppressFullScreenIntent(key)) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); + } + } else if (mNotificationData.getImportance(key) + < NotificationManager.IMPORTANCE_HIGH) { + if (DEBUG) { + Log.d(TAG, "No Fullscreen intent: not important enough: " + + key); + } + } else { + // Stop screensaver if the notification has a fullscreen intent. + // (like an incoming phone call) + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + + // not immersive & a fullscreen alert should be shown + if (DEBUG) + Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); + try { + EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, + key); + notification.getNotification().fullScreenIntent.send(); + shadeEntry.notifyFullScreenIntentLaunched(); + mMetricsLogger.count("note_fullscreen", 1); + } catch (PendingIntent.CanceledException e) { + } + } + } + abortExistingInflation(key); + + mForegroundServiceController.addNotification(notification, + mNotificationData.getImportance(key)); + + mPendingNotifications.put(key, shadeEntry); + } + + @Override + public void addNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + addNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) { + return oldEntry == null || !oldEntry.hasInterrupted() + || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; + } + + private void updateNotificationInternal(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) throws InflationException { + if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); + + final String key = notification.getKey(); + abortExistingInflation(key); + NotificationData.Entry entry = mNotificationData.get(key); + if (entry == null) { + return; + } + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + mRemoteInputManager.onUpdateNotification(entry); + + if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { + mGutsManager.setKeyToRemoveOnGutsClosed(null); + Log.w(TAG, "Notification that was kept for guts was updated. " + key); + } + + Notification n = notification.getNotification(); + mNotificationData.updateRanking(ranking); + + final StatusBarNotification oldNotification = entry.notification; + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, oldNotification); + + entry.updateIcons(mContext, notification); + inflateViews(entry, mStackScroller); + + mForegroundServiceController.updateNotification(notification, + mNotificationData.getImportance(key)); + + boolean shouldPeek = shouldPeek(entry, notification); + boolean alertAgain = alertAgain(entry, n); + + updateHeadsUp(key, entry, shouldPeek, alertAgain); + updateNotifications(); + + 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); + } + + if (DEBUG) { + // Is this for you? + boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification); + Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); + } + + mCallback.onNotificationUpdated(notification); + } + + @Override + public void updateNotification(StatusBarNotification notification, + NotificationListenerService.RankingMap ranking) { + try { + updateNotificationInternal(notification, ranking); + } catch (InflationException e) { + handleInflationException(notification, e); + } + } + + public void updateNotifications() { + mNotificationData.filterAndSort(); + + mPresenter.updateNotificationViews(); + } + + public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) { + mNotificationData.updateRanking(ranking); + updateNotifications(); + } + + protected boolean shouldPeek(NotificationData.Entry entry) { + return shouldPeek(entry, entry.notification); + } + + public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { + if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { + if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); + return false; + } + + if (mNotificationData.shouldFilterOut(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); + return false; + } + + boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); + + if (!inUse && !mPresenter.isDozing()) { + if (DEBUG) { + Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); + } + return false; + } + + if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); + return false; + } + + if (entry.hasJustLaunchedFullScreenIntent()) { + if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); + return false; + } + + if (isSnoozedPackage(sbn)) { + if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); + return false; + } + + // Allow peeking for DEFAULT notifications only if we're on Ambient Display. + int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_HIGH; + if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { + if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); + return false; + } + + // Don't peek notifications that are suppressed due to group alert behavior + if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { + if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); + return false; + } + + if (!mCallback.shouldPeek(entry, sbn)) { + return false; + } + + return true; + } + + protected void setNotificationShown(StatusBarNotification n) { + setNotificationsShown(new String[]{n.getKey()}); + } + + protected void setNotificationsShown(String[] keys) { + try { + mNotificationListener.setNotificationsShown(keys); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); + } + } + + protected boolean isSnoozedPackage(StatusBarNotification sbn) { + return mHeadsUpManager.isSnoozed(sbn.getPackageName()); + } + + protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + if (!shouldPeek) { + // We don't want this to be interrupting anymore, lets remove it + mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); + } else { + mHeadsUpManager.updateNotification(entry, alertAgain); + } + } else if (shouldPeek && alertAgain) { + // This notification was updated to be a heads-up, show it! + mHeadsUpManager.showNotification(entry); + } + } + + protected boolean isHeadsUp(String key) { + return mHeadsUpManager.isHeadsUp(key); + } + + /** + * Callback for NotificationEntryManager. + */ + public interface Callback { + + /** + * Called when a new entry is created. + * + * @param shadeEntry entry that was created + */ + void onNotificationAdded(NotificationData.Entry shadeEntry); + + /** + * Called when a notification was updated. + * + * @param notification notification that was updated + */ + void onNotificationUpdated(StatusBarNotification notification); + + /** + * Called when a notification was removed. + * + * @param key key of notification that was removed + * @param old StatusBarNotification of the notification before it was removed + */ + void onNotificationRemoved(String key, StatusBarNotification old); + + + /** + * Called when a notification is clicked. + * + * @param sbn notification that was clicked + * @param row row for that notification + */ + void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Called when a new notification and row is created. + * + * @param entry entry for the notification + * @param pmUser package manager for user + * @param sbn notification + * @param row row for the notification + */ + void onBindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row); + + /** + * Removes a notification immediately. + * + * @param statusBarNotification notification that is being removed + */ + void onPerformRemoveNotification(StatusBarNotification statusBarNotification); + + /** + * Returns true if NotificationEntryManager should peek this notification. + * + * @param entry entry of the notification that might be peeked + * @param sbn notification that might be peeked + * @return true if the notification should be peeked + */ + boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java index 2e572e17557a..d6a8af5cedd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java @@ -168,7 +168,8 @@ public class NotificationGutsManager implements Dumpable { String key = sbn.getKey(); if (key.equals(mKeyToRemoveOnGutsClosed)) { mKeyToRemoveOnGutsClosed = null; - mPresenter.removeNotification(key, mPresenter.getLatestRankingMap()); + mPresenter.getEntryManager().removeNotification(key, + mPresenter.getEntryManager().getLatestRankingMap()); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index a72e8ac77af5..cd51f03fcba9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -59,7 +59,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { final RankingMap currentRanking = getCurrentRanking(); mPresenter.getHandler().post(() -> { for (StatusBarNotification sbn : notifications) { - mPresenter.addNotification(sbn, currentRanking); + mPresenter.getEntryManager().addNotification(sbn, currentRanking); } }); } @@ -73,7 +73,8 @@ public class NotificationListener extends NotificationListenerWithPlugins { processForRemoteInput(sbn.getNotification(), mContext); String key = sbn.getKey(); mRemoteInputManager.getKeysKeptForRemoteInput().remove(key); - boolean isUpdate = mPresenter.getNotificationData().get(key) != null; + boolean isUpdate = + mPresenter.getEntryManager().getNotificationData().get(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since` we're not going to show them // anyway. This is true also when the summary is canceled, @@ -86,16 +87,17 @@ public class NotificationListener extends NotificationListenerWithPlugins { // Remove existing notification to avoid stale data. if (isUpdate) { - mPresenter.removeNotification(key, rankingMap); + mPresenter.getEntryManager().removeNotification(key, rankingMap); } else { - mPresenter.getNotificationData().updateRanking(rankingMap); + mPresenter.getEntryManager().getNotificationData() + .updateRanking(rankingMap); } return; } if (isUpdate) { - mPresenter.updateNotification(sbn, rankingMap); + mPresenter.getEntryManager().updateNotification(sbn, rankingMap); } else { - mPresenter.addNotification(sbn, rankingMap); + mPresenter.getEntryManager().addNotification(sbn, rankingMap); } }); } @@ -107,7 +109,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) { final String key = sbn.getKey(); - mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap)); + mPresenter.getHandler().post(() -> { + mPresenter.getEntryManager().removeNotification(key, rankingMap); + }); } } @@ -116,7 +120,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { if (DEBUG) Log.d(TAG, "onRankingUpdate"); if (rankingMap != null) { RankingMap r = onPluginRankingUpdate(rankingMap); - mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r)); + mPresenter.getHandler().post(() -> { + mPresenter.getEntryManager().updateNotificationRanking(r); + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index 644d834e42c6..25eb9cccf4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -81,7 +81,7 @@ public class NotificationLockscreenUserManager implements Dumpable { isCurrentProfile(getSendingUserId())) { mUsersAllowingPrivateNotifications.clear(); updateLockscreenNotificationSetting(); - mPresenter.updateNotifications(); + mPresenter.getEntryManager().updateNotifications(); } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { if (userId != mCurrentUserId && isCurrentProfile(userId)) { mPresenter.onWorkChallengeChanged(); @@ -182,7 +182,7 @@ public class NotificationLockscreenUserManager implements Dumpable { mUsersAllowingNotifications.clear(); // ... and refresh all the notifications updateLockscreenNotificationSetting(); - mPresenter.updateNotifications(); + mPresenter.getEntryManager().updateNotifications(); } }; @@ -191,7 +191,7 @@ public class NotificationLockscreenUserManager implements Dumpable { public void onChange(boolean selfChange) { updateLockscreenNotificationSetting(); if (mDeviceProvisionedController.isDeviceProvisioned()) { - mPresenter.updateNotifications(); + mPresenter.getEntryManager().updateNotifications(); } } }; @@ -271,13 +271,13 @@ public class NotificationLockscreenUserManager implements Dumpable { */ public boolean shouldHideNotifications(String key) { return isLockscreenPublicMode(mCurrentUserId) - && mPresenter.getNotificationData().getVisibilityOverride(key) == + && mPresenter.getEntryManager().getNotificationData().getVisibilityOverride(key) == Notification.VISIBILITY_SECRET; } public boolean shouldShowOnKeyguard(StatusBarNotification sbn) { return mShowLockscreenNotifications - && !mPresenter.getNotificationData().isAmbient(sbn.getKey()); + && !mPresenter.getEntryManager().getNotificationData().isAmbient(sbn.getKey()); } private void setShowLockscreenNotifications(boolean show) { @@ -395,7 +395,7 @@ public class NotificationLockscreenUserManager implements Dumpable { } private boolean packageHasVisibilityOverride(String key) { - return mPresenter.getNotificationData().getVisibilityOverride(key) == + return mPresenter.getEntryManager().getNotificationData().getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java index e58d80186b3a..e958f3fe54d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java @@ -99,7 +99,7 @@ public class NotificationLogger { // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. - ArrayList<NotificationData.Entry> activeNotifications = mPresenter. + ArrayList<NotificationData.Entry> activeNotifications = mPresenter.getEntryManager(). getNotificationData().getActiveNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 283a6e385582..158a986f2cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -40,9 +40,10 @@ public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; - private final NotificationPresenter mPresenter; private final Context mContext; private final MediaSessionManager mMediaSessionManager; + + private NotificationPresenter mPresenter; private MediaController mMediaController; private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; @@ -73,8 +74,7 @@ public class NotificationMediaManager implements Dumpable { } }; - public NotificationMediaManager(NotificationPresenter presenter, Context context) { - mPresenter = presenter; + public NotificationMediaManager(Context context) { mContext = context; mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); @@ -82,6 +82,10 @@ public class NotificationMediaManager implements Dumpable { // in session state } + public void setUpWithPresenter(NotificationPresenter presenter) { + mPresenter = presenter; + } + public void onNotificationRemoved(String key) { if (key.equals(mMediaNotificationKey)) { clearCurrentMediaNotification(); @@ -100,8 +104,8 @@ public class NotificationMediaManager implements Dumpable { public void findAndUpdateMediaNotifications() { boolean metaDataChanged = false; - synchronized (mPresenter.getNotificationData()) { - ArrayList<NotificationData.Entry> activeNotifications = mPresenter + synchronized (mPresenter.getEntryManager().getNotificationData()) { + ArrayList<NotificationData.Entry> activeNotifications = mPresenter.getEntryManager() .getNotificationData().getActiveNotifications(); final int N = activeNotifications.size(); @@ -188,7 +192,7 @@ public class NotificationMediaManager implements Dumpable { } if (metaDataChanged) { - mPresenter.updateNotifications(); + mPresenter.getEntryManager().updateNotifications(); } mPresenter.updateMediaMetaData(metaDataChanged, true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 33c72534b1b6..c1dd958506f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -16,12 +16,11 @@ package com.android.systemui.statusbar; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Handler; -import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.view.View; -import java.util.Set; - /** * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods * for both querying the state of the system (some modularised piece of functionality may @@ -29,9 +28,11 @@ import java.util.Set; * for affecting the state of the system (e.g. starting an intent, given that the presenter may * want to perform some action before doing so). */ -public interface NotificationPresenter extends NotificationUpdateHandler, - NotificationData.Environment, NotificationRemoteInputManager.Callback { - +public interface NotificationPresenter extends NotificationData.Environment, + NotificationRemoteInputManager.Callback, + ExpandableNotificationRow.OnExpandClickListener, + ActivatableNotificationView.OnActivatedListener, + NotificationEntryManager.Callback { /** * Returns true if the presenter is not visible. For example, it may not be necessary to do * animations if this returns true. @@ -50,32 +51,15 @@ public interface NotificationPresenter extends NotificationUpdateHandler, void startNotificationGutsIntent(Intent intent, int appUid); /** - * Returns NotificationData. - */ - NotificationData getNotificationData(); - - /** * Returns the Handler for NotificationPresenter. */ Handler getHandler(); - // TODO: Create NotificationEntryManager and move this method to there. - /** - * Signals that some notifications have changed, and NotificationPresenter should update itself. - */ - void updateNotifications(); - /** * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. */ void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation); - // TODO: Create NotificationEntryManager and move this method to there. - /** - * Gets the latest ranking map. - */ - NotificationListenerService.RankingMap getLatestRankingMap(); - /** * Called when the locked status of the device is changed for a work profile. */ @@ -107,4 +91,27 @@ public interface NotificationPresenter extends NotificationUpdateHandler, * @return true iff the device is locked */ boolean isDeviceLocked(int userId); + + /** + * @return true iff the device is in vr mode + */ + boolean isDeviceInVrMode(); + + /** + * Returns the NotificationEntryManager for this presenter. + * + * @return NotificationEntryManager + */ + NotificationEntryManager getEntryManager(); + + // TODO: Remove this once the view managing code is pulled out of StatusBar. + /** + * Updates the visual representation of the notifications. + */ + void updateNotificationViews(); + + /** + * @return true iff the device is dozing + */ + boolean isDozing(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 7827f62970e7..d0428d0ee51d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -282,7 +282,7 @@ public class NotificationRemoteInputManager implements Dumpable { @Override public void onRemoteInputSent(NotificationData.Entry entry) { if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { - mPresenter.removeNotification(entry.key, null); + mPresenter.getEntryManager().removeNotification(entry.key, null); } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { // We're currently holding onto this notification, but from the apps point of // view it is already canceled, so we'll need to cancel it on the apps behalf @@ -290,7 +290,7 @@ public class NotificationRemoteInputManager implements Dumpable { // bit. mPresenter.getHandler().postDelayed(() -> { if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { - mPresenter.removeNotification(entry.key, null); + mPresenter.getEntryManager().removeNotification(entry.key, null); } }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); } @@ -336,7 +336,8 @@ public class NotificationRemoteInputManager implements Dumpable { for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); mRemoteInputController.removeRemoteInput(entry, null); - mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap()); + mPresenter.getEntryManager().removeNotification(entry.key, + mPresenter.getEntryManager().getLatestRankingMap()); } mRemoteInputEntriesToRemoveOnCollapse.clear(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 5ba6f6af05ab..3ebeb4d45c26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -247,19 +247,6 @@ public class CarStatusBar extends StatusBar implements return null; } - /** - * Returns the - * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will - * be triggered when a notification card is long-pressed. - */ - @Override - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - // For the automative use case, we do not want to the user to be able to interact with - // a notification other than a regular click. As a result, just return null for the - // long click listener. - return null; - } - @Override public void showBatteryView() { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -388,18 +375,6 @@ public class CarStatusBar extends StatusBar implements } @Override - protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. Ensure this by not pinning any - // notification if the shade is already opened. - if (mPanelExpanded) { - return false; - } - - return super.shouldPeek(entry, sbn); - } - - @Override public void animateExpandNotificationsPanel() { // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- 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 c5349d186b86..04fe7f206021 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -29,9 +29,6 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager 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.NotificationRemoteInputManager - .FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; 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; @@ -66,14 +63,12 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; @@ -88,7 +83,6 @@ import android.media.MediaMetadata; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -103,12 +97,9 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; -import android.service.notification.NotificationListenerService.RankingMap; -import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; -import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -139,7 +130,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.MessagingGroup; import com.android.internal.widget.MessagingMessage; @@ -149,11 +139,9 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.AutoReinflateContainer; -import com.android.systemui.DejankUtils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; -import com.android.systemui.ForegroundServiceController; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -200,6 +188,7 @@ import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; 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.NotificationInfo; import com.android.systemui.statusbar.NotificationListener; @@ -214,8 +203,6 @@ import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; -import com.android.systemui.statusbar.notification.InflationException; -import com.android.systemui.statusbar.notification.RowInflaterTask; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; @@ -239,7 +226,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -255,9 +241,6 @@ import java.util.Stack; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks, - ActivatableNotificationView.OnActivatedListener, - ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, - ExpandableNotificationRow.OnExpandClickListener, InflationCallback, ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter { public static final boolean MULTIUSER_DEBUG = false; @@ -270,10 +253,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026; protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027; - protected static final boolean ENABLE_HEADS_UP = true; - protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; - - // Should match the values in PhoneWindowManager public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; @@ -309,7 +288,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Time after we abort the launch transition. private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000; - private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; + protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; private static final int STATUS_OR_NAV_TRANSIENT = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT; @@ -427,6 +406,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationGutsManager mGutsManager; protected NotificationLogger mNotificationLogger; + protected NotificationEntryManager mEntryManager; // for disabling the status bar private int mDisabled1 = 0; @@ -478,23 +458,6 @@ public class StatusBar extends SystemUI implements DemoMode, }; protected final H mHandler = createHandler(); - final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - boolean wasUsing = mUseHeadsUp; - mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts - && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, - Settings.Global.HEADS_UP_OFF); - Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); - if (wasUsing != mUseHeadsUp) { - if (!mUseHeadsUp) { - Log.d(TAG, "dismissing any existing heads up notification on disable event"); - mHeadsUpManager.releaseAllImmediately(); - } - } - } - }; private int mInteractingWindows; private boolean mAutohideSuspended; @@ -588,7 +551,6 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private NotificationMessagingUtil mMessagingUtil; private KeyguardUserSwitcher mKeyguardUserSwitcher; private UserSwitcherController mUserSwitcherController; private NetworkController mNetworkController; @@ -603,11 +565,9 @@ public class StatusBar extends SystemUI implements DemoMode, private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); protected NotificationIconAreaController mNotificationIconAreaController; private boolean mReinflateNotificationsOnUserSwitched; - private final HashMap<String, Entry> mPendingNotifications = new HashMap<>(); private boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; private SysuiColorExtractor mColorExtractor; - private ForegroundServiceController mForegroundServiceController; private ScreenLifecycle mScreenLifecycle; @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle; @@ -619,7 +579,6 @@ public class StatusBar extends SystemUI implements DemoMode, }; private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap = new HashMap<>(); - private RankingMap mLatestRankingMap; private boolean mNoAnimationOnNextBarModeChange; private FalsingManager mFalsingManager; @@ -641,6 +600,8 @@ public class StatusBar extends SystemUI implements DemoMode, mGroupManager = Dependency.get(NotificationGroupManager.class); mNotificationLogger = Dependency.get(NotificationLogger.class); mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); + mNotificationListener = Dependency.get(NotificationListener.class); + mGroupManager = Dependency.get(NotificationGroupManager.class); mNetworkController = Dependency.get(NetworkController.class); mUserSwitcherController = Dependency.get(UserSwitcherController.class); mScreenLifecycle = Dependency.get(ScreenLifecycle.class); @@ -649,19 +610,18 @@ public class StatusBar extends SystemUI implements DemoMode, mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mBatteryController = Dependency.get(BatteryController.class); mAssistManager = Dependency.get(AssistManager.class); - mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); mGutsManager = Dependency.get(NotificationGutsManager.class); + mMediaManager = Dependency.get(NotificationMediaManager.class); + mEntryManager = Dependency.get(NotificationEntryManager.class); mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mForegroundServiceController = Dependency.get(ForegroundServiceController.class); - mDisplay = mWindowManager.getDefaultDisplay(); updateDisplaySize(); @@ -679,16 +639,12 @@ public class StatusBar extends SystemUI implements DemoMode, mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); - mNotificationData = new NotificationData(this); - mMessagingUtil = new NotificationMessagingUtil(mContext); - mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); @@ -698,8 +654,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mLockPatternUtils = new LockPatternUtils(mContext); - mMediaManager = new NotificationMediaManager(this, mContext); - mLockscreenUserManager.setUpWithPresenter(this); + mMediaManager.setUpWithPresenter(this); // Connect in to the status bar manager service mCommandQueue = getComponent(CommandQueue.class); @@ -725,6 +680,7 @@ public class StatusBar extends SystemUI implements DemoMode, mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter); mWallpaperChangedReceiver.onReceive(mContext, null); + mLockscreenUserManager.setUpWithPresenter(this); mCommandQueue.disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, fullscreenStackBounds, dockedStackBounds); @@ -739,7 +695,6 @@ public class StatusBar extends SystemUI implements DemoMode, } // Set up the initial notification state. - mNotificationListener = Dependency.get(NotificationListener.class); mNotificationListener.setUpWithPresenter(this); if (DEBUG) { @@ -774,15 +729,6 @@ public class StatusBar extends SystemUI implements DemoMode, // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); - mHeadsUpObserver.onChange(true); // set up - if (ENABLE_HEADS_UP) { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), true, - mHeadsUpObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, - mHeadsUpObserver); - } mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); mUnlockMethodCache.addListener(this); startKeyguard(); @@ -864,11 +810,13 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); - mNotificationData.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); + mEntryManager.setUpWithPresenter(this, mStackScroller, this, mVisualStabilityManager, + mHeadsUpManager); + if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info); mNotificationPanelDebugText.setVisibility(View.VISIBLE); @@ -884,7 +832,7 @@ public class StatusBar extends SystemUI implements DemoMode, // no window manager? good luck with that } - mStackScroller.setLongPressListener(getNotificationLongClicker()); + mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker()); mStackScroller.setStatusBar(this); mStackScroller.setGroupManager(mGroupManager); mStackScroller.setHeadsUpManager(mHeadsUpManager); @@ -1107,7 +1055,7 @@ public class StatusBar extends SystemUI implements DemoMode, MessagingGroup.dropCache(); // start old BaseStatusBar.onDensityOrFontScaleChanged(). if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); } else { mReinflateNotificationsOnUserSwitched = true; } @@ -1171,20 +1119,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - private void updateNotificationsOnDensityOrFontScaleChanged() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - for (int i = 0; i < activeNotifications.size(); i++) { - Entry entry = activeNotifications.get(i); - boolean exposedGuts = mGutsManager.getExposedGuts() != null - && entry.row.getGuts() == mGutsManager.getExposedGuts(); - entry.row.onDensityOrFontScaleChanged(); - if (exposedGuts) { - mGutsManager.setExposedGuts(entry.row.getGuts()); - mGutsManager.bindGuts(entry.row); - } - } - } - private void inflateSignalClusters() { if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar); } @@ -1298,7 +1232,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setDismissAllInProgress(false); for (ExpandableNotificationRow rowToRemove : viewsToRemove) { if (mStackScroller.canChildBeDismissed(rowToRemove)) { - removeNotification(rowToRemove.getEntry().key, null); + mEntryManager.removeNotification(rowToRemove.getEntry().key, null); } else { rowToRemove.resetTranslation(); } @@ -1406,303 +1340,29 @@ public class StatusBar extends SystemUI implements DemoMode, return true; } - void awakenDreams() { - SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); - } - - @Override - public void addNotification(StatusBarNotification notification, RankingMap ranking) { - String key = notification.getKey(); - if (DEBUG) Log.d(TAG, "addNotification key=" + key); - - mNotificationData.updateRanking(ranking); - Entry shadeEntry = null; - try { - shadeEntry = createNotificationViews(notification); - } catch (InflationException e) { - handleInflationException(notification, e); - return; - } - boolean isHeadsUped = shouldPeek(shadeEntry); - if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { - if (shouldSuppressFullScreenIntent(key)) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); - } - } else if (mNotificationData.getImportance(key) - < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: not important enough: " - + key); - } - } else { - // Stop screensaver if the notification has a fullscreen intent. - // (like an incoming phone call) - awakenDreams(); - - // not immersive & a fullscreen alert should be shown - if (DEBUG) - Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - try { - EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, - key); - notification.getNotification().fullScreenIntent.send(); - shadeEntry.notifyFullScreenIntentLaunched(); - mMetricsLogger.count("note_fullscreen", 1); - } catch (PendingIntent.CanceledException e) { - } - } - } - abortExistingInflation(key); - - mForegroundServiceController.addNotification(notification, - mNotificationData.getImportance(key)); - - mPendingNotifications.put(key, shadeEntry); - } - - private void abortExistingInflation(String key) { - if (mPendingNotifications.containsKey(key)) { - Entry entry = mPendingNotifications.get(key); - entry.abortTask(); - mPendingNotifications.remove(key); - } - Entry addedEntry = mNotificationData.get(key); - if (addedEntry != null) { - addedEntry.abortTask(); - } - } - - private void addEntry(Entry shadeEntry) { - boolean isHeadsUped = shouldPeek(shadeEntry); - if (isHeadsUped) { - mHeadsUpManager.showNotification(shadeEntry); - // Mark as seen immediately - setNotificationShown(shadeEntry.notification); - } - addNotificationViews(shadeEntry); - // Recalculate the position of the sliding windows and the titles. - setAreThereNotifications(); - } - - @Override - public void handleInflationException(StatusBarNotification notification, Exception e) { - handleNotificationError(notification, e.getMessage()); - } - - @Override - public void onAsyncInflationFinished(Entry entry) { - mPendingNotifications.remove(entry.key); - // If there was an async task started after the removal, we don't want to add it back to - // the list, otherwise we might get leaks. - boolean isNew = mNotificationData.get(entry.key) == null; - if (isNew && !entry.row.isRemoved()) { - addEntry(entry); - } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { - mVisualStabilityManager.onLowPriorityUpdated(entry); - updateNotificationShade(); - } - entry.row.setLowPriorityStateUpdated(false); - } - - private boolean shouldSuppressFullScreenIntent(String key) { - if (isDeviceInVrMode()) { - return true; - } - - if (mPowerManager.isInteractive()) { - return mNotificationData.shouldSuppressScreenOn(key); - } else { - return mNotificationData.shouldSuppressScreenOff(key); - } - } - - @Override - public void updateNotificationRanking(RankingMap ranking) { - mNotificationData.updateRanking(ranking); - updateNotifications(); - } - @Override - public void removeNotification(String key, RankingMap ranking) { - boolean deferRemoval = false; - abortExistingInflation(key); - if (mHeadsUpManager.isHeadsUp(key)) { - // A cancel() in response to a remote input shouldn't be delayed, as it makes the - // sending look longer than it takes. - // Also we should not defer the removal if reordering isn't allowed since otherwise - // some notifications can't disappear before the panel is closed. - boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) - && !FORCE_REMOTE_INPUT_HISTORY - || !mVisualStabilityManager.isReorderingAllowed(); - deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); - } - mMediaManager.onNotificationRemoved(key); - - Entry entry = mNotificationData.get(key); - if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key) - && entry.row != null && !entry.row.isDismissed()) { - StatusBarNotification sbn = entry.notification; - - Notification.Builder b = Notification.Builder - .recoverBuilder(mContext, sbn.getNotification().clone()); - CharSequence[] oldHistory = sbn.getNotification().extras - .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); - CharSequence[] newHistory; - if (oldHistory == null) { - newHistory = new CharSequence[1]; - } else { - newHistory = new CharSequence[oldHistory.length + 1]; - System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); - } - newHistory[0] = String.valueOf(entry.remoteInputText); - b.setRemoteInputHistory(newHistory); - - Notification newNotification = b.build(); - - // Undo any compatibility view inflation - newNotification.contentView = sbn.getNotification().contentView; - newNotification.bigContentView = sbn.getNotification().bigContentView; - newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; - - StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); - boolean updated = false; - try { - updateNotificationInternal(newSbn, null); - updated = true; - } catch (InflationException e) { - deferRemoval = false; - } - if (updated) { - Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); - mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); - return; - } - } - if (deferRemoval) { - mLatestRankingMap = ranking; - mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); - return; - } - - if (mRemoteInputManager.onRemoveNotification(entry)) { - mLatestRankingMap = ranking; - return; - } - - if (entry != null && mGutsManager.getExposedGuts() != null - && mGutsManager.getExposedGuts() == entry.row.getGuts() - && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { - Log.w(TAG, "Keeping notification because it's showing guts. " + key); - mLatestRankingMap = ranking; - mGutsManager.setKeyToRemoveOnGutsClosed(key); - return; - } - - if (entry != null) { - mForegroundServiceController.removeNotification(entry.notification); - } - - if (entry != null && entry.row != null) { - entry.row.setRemoved(); - mStackScroller.cleanUpViewState(entry.row); - } - // Let's remove the children if this was a summary - handleGroupSummaryRemoved(key); - StatusBarNotification old = removeNotificationViews(key, ranking); - if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); - - if (old != null) { - if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() - && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { - if (mState == StatusBarState.SHADE) { - animateCollapsePanels(); - } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) { - goToKeyguard(); - } - } - } - setAreThereNotifications(); - } - - /** - * Ensures that the group children are cancelled immediately when the group summary is cancelled - * instead of waiting for the notification manager to send all cancels. Otherwise this could - * lead to flickers. - * - * This also ensures that the animation looks nice and only consists of a single disappear - * animation instead of multiple. - * @param key the key of the notification was removed - * - */ - private void handleGroupSummaryRemoved(String key) { - Entry entry = mNotificationData.get(key); - if (entry != null && entry.row != null - && entry.row.isSummaryWithChildren()) { - if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { - // We don't want to remove children for autobundled notifications as they are not - // always cancelled. We only remove them if they were dismissed by the user. - return; - } - List<ExpandableNotificationRow> notificationChildren = - entry.row.getNotificationChildren(); - for (int i = 0; i < notificationChildren.size(); i++) { - ExpandableNotificationRow row = notificationChildren.get(i); - if ((row.getStatusBarNotification().getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - // the child is a foreground service notification which we can't remove! - continue; - } - row.setKeepInParent(true); - // we need to set this state earlier as otherwise we might generate some weird - // animations - row.setRemoved(); - } - } - } - - protected void performRemoveNotification(StatusBarNotification n) { - Entry entry = mNotificationData.get(n.getKey()); - mRemoteInputManager.onPerformRemoveNotification(n, entry); - // start old BaseStatusBar.performRemoveNotification. - final String pkg = n.getPackageName(); - final String tag = n.getTag(); - final int id = n.getId(); - final int userId = n.getUserId(); - try { - int dismissalSurface = NotificationStats.DISMISSAL_SHADE; - if (isHeadsUp(n.getKey())) { - dismissalSurface = NotificationStats.DISMISSAL_PEEK; - } else if (mStackScroller.hasPulsingNotifications()) { - dismissalSurface = NotificationStats.DISMISSAL_AOD; - } - mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); - removeNotification(n.getKey(), null); - - } catch (RemoteException ex) { - // system process is dead if we're here. - } + public void onPerformRemoveNotification(StatusBarNotification n) { if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); } - // end old BaseStatusBar.performRemoveNotification. } - private void updateNotificationShade() { - if (mStackScroller == null) return; + @Override + public void updateNotificationViews() { + // The function updateRowStates depends on both of these being non-null, so check them here. + // We may be called before they are set from DeviceProvisionedController's callback. + if (mStackScroller == null || mScrimController == null) return; // Do not modify the notifications during collapse. if (isCollapsing()) { - addPostCollapseAction(this::updateNotificationShade); + addPostCollapseAction(this::updateNotificationViews); return; } - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + 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++) { @@ -1814,7 +1474,36 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); // Let's also update the icons - mNotificationIconAreaController.updateNotificationIcons(mNotificationData); + mNotificationIconAreaController.updateNotificationIcons( + mEntryManager.getNotificationData()); + } + + @Override + public void onNotificationAdded(Entry shadeEntry) { + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + } + + @Override + public void onNotificationUpdated(StatusBarNotification notification) { + setAreThereNotifications(); + } + + @Override + public void onNotificationRemoved(String key, StatusBarNotification old) { + if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); + + if (old != null) { + if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() + && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { + if (mState == StatusBarState.SHADE) { + animateCollapsePanels(); + } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) { + goToKeyguard(); + } + } + } + setAreThereNotifications(); } /** @@ -1896,7 +1585,8 @@ public class StatusBar extends SystemUI implements DemoMode, } for (ExpandableNotificationRow remove : toRemove) { parent.removeChildNotification(remove); - if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) { + 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, @@ -1949,7 +1639,7 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateEmptyShadeView() { boolean showEmptyShadeView = mState != StatusBarState.KEYGUARD && - mNotificationData.getActiveNotifications().size() == 0; + mEntryManager.getNotificationData().getActiveNotifications().size() == 0; mNotificationPanel.showEmptyShadeView(showEmptyShadeView); } @@ -1964,7 +1654,8 @@ public class StatusBar extends SystemUI implements DemoMode, } ExpandableNotificationRow row = (ExpandableNotificationRow) view; currentIndex++; - if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) { + if (!mEntryManager.getNotificationData().isAmbient( + row.getStatusBarNotification().getKey())) { speedBumpIndex = currentIndex; } } @@ -1976,15 +1667,9 @@ public class StatusBar extends SystemUI implements DemoMode, return entry.row.getParent() instanceof NotificationStackScrollLayout; } - @Override - public void updateNotifications() { - mNotificationData.filterAndSort(); - - updateNotificationShade(); - } public void requestNotificationUpdate() { - updateNotifications(); + mEntryManager.updateNotifications(); } protected void setAreThereNotifications() { @@ -1993,7 +1678,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean clearable = hasActiveNotifications() && hasActiveClearableNotifications(); Log.d(TAG, "setAreThereNotifications: N=" + - mNotificationData.getActiveNotifications().size() + " any=" + + mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" + hasActiveNotifications() + " clearable=" + clearable); } @@ -2278,9 +1963,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - mDisableNotificationAlerts = - (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - mHeadsUpObserver.onChange(true); + mEntryManager.setDisableNotificationAlerts( + (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0); } if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) { @@ -2343,10 +2027,40 @@ public class StatusBar extends SystemUI implements DemoMode, return getBarState() == StatusBarState.KEYGUARD; } + @Override public boolean isDozing() { return mDozing; } + @Override + public boolean shouldPeek(Entry entry, StatusBarNotification sbn) { + if (mIsOccluded && !isDozing()) { + boolean devicePublic = mLockscreenUserManager. + isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); + boolean userPublic = devicePublic + || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); + boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); + if (userPublic && needsRedaction) { + return false; + } + } + + if (sbn.getNotification().fullScreenIntent != null) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { + if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); + return false; + } else if (isDozing()) { + // We never want heads up when we are dozing. + return false; + } else { + // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent + return !mStatusBarKeyguardViewManager.isShowing() + || mStatusBarKeyguardViewManager.isOccluded(); + } + } + return true; + } + @Override // NotificationData.Environment public String getCurrentMediaNotificationKey() { return mMediaManager.getMediaNotificationKey(); @@ -2415,34 +2129,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { - if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { - removeNotification(entry.key, mLatestRankingMap); - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { - mLatestRankingMap = null; - } - } else { - updateNotificationRanking(null); - if (isHeadsUp) { - mDozeServiceHost.fireNotificationHeadsUp(); - } - } - - } + mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp); - protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek, - boolean alertAgain) { - final boolean wasHeadsUp = isHeadsUp(key); - if (wasHeadsUp) { - if (!shouldPeek) { - // We don't want this to be interrupting anymore, lets remove it - mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); - } else { - mHeadsUpManager.updateNotification(entry, alertAgain); - } - } else if (shouldPeek && alertAgain) { - // This notification was updated to be a heads-up, show it! - mHeadsUpManager.showNotification(entry); + if (isHeadsUp) { + mDozeServiceHost.fireNotificationHeadsUp(); } } @@ -2452,14 +2142,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - public boolean isHeadsUp(String key) { - return mHeadsUpManager.isHeadsUp(key); - } - - protected boolean isSnoozedPackage(StatusBarNotification sbn) { - return mHeadsUpManager.isSnoozed(sbn.getPackageName()); - } - public boolean isKeyguardCurrentlySecure() { return !mUnlockMethodCache.canSkipBouncer(); } @@ -2491,7 +2173,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onReorderingAllowed() { - updateNotifications(); + mEntryManager.updateNotifications(); } public boolean isLaunchTransitionFadingAway() { @@ -3127,14 +2809,6 @@ public class StatusBar extends SystemUI implements DemoMode, + " scroll " + mStackScroller.getScrollX() + "," + mStackScroller.getScrollY()); } - pw.print(" mPendingNotifications="); - if (mPendingNotifications.size() == 0) { - pw.println("null"); - } else { - for (Entry entry : mPendingNotifications.values()) { - pw.println(entry.notification); - } - } pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); @@ -3146,8 +2820,7 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(Settings.Global.zenModeToString(Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF))); - pw.print(" mUseHeadsUp="); - pw.println(mUseHeadsUp); + if (mStatusBarView != null) { dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); } @@ -3189,8 +2862,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if (DUMPTRUCK) { - synchronized (mNotificationData) { - mNotificationData.dump(pw, " "); + synchronized (mEntryManager.getNotificationData()) { + mEntryManager.getNotificationData().dump(pw, " "); } if (false) { @@ -3429,7 +3102,8 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData() + .getActiveNotifications(); final int notificationCount = activeNotifications.size(); for (int i = 0; i < notificationCount; i++) { NotificationData.Entry entry = activeNotifications.get(i); @@ -3484,12 +3158,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); updatePublicMode(); - mNotificationData.filterAndSort(); + mEntryManager.getNotificationData().filterAndSort(); if (mReinflateNotificationsOnUserSwitched) { - updateNotificationsOnDensityOrFontScaleChanged(); + mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); mReinflateNotificationsOnUserSwitched = false; } - updateNotificationShade(); + updateNotificationViews(); mMediaManager.clearCurrentMediaNotification(); setLockscreenUser(newUserId); } @@ -3499,6 +3173,13 @@ public class StatusBar extends SystemUI implements DemoMode, return mLockscreenUserManager; } + @Override + public void onBindRow(Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row) { + row.setAboveShelfChangedListener(mAboveShelfObserver); + row.setSecureStateProvider(this::isKeyguardCurrentlySecure); + } + protected void setLockscreenUser(int newUserId) { mLockscreenWallpaper.setCurrentUser(newUserId); mScrimController.setCurrentUser(newUserId); @@ -3559,7 +3240,8 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mNotificationData.getActiveNotifications().size(); + int notificationLoad = mEntryManager.getNotificationData() + .getActiveNotifications().size(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -3577,7 +3259,8 @@ public class StatusBar extends SystemUI implements DemoMode, !isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationData.getActiveNotifications().size(); + int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications() + .size(); if (pinnedHeadsUp && isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3712,7 +3395,7 @@ public class StatusBar extends SystemUI implements DemoMode, } catch (RemoteException e) { // Ignore. } - mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); + mEntryManager.destroy(); // End old BaseStatusBar.destroy(). if (mStatusBarWindow != null) { mWindowManager.removeViewImmediate(mStatusBarWindow); @@ -4165,7 +3848,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateDozingState(); updatePublicMode(); updateStackScrollerState(goingToFullShade, fromShadeLocked); - updateNotifications(); + mEntryManager.updateNotifications(); checkBarModes(); updateScrimController(); updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); @@ -4679,7 +4362,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onWorkChallengeChanged() { updatePublicMode(); - updateNotifications(); + mEntryManager.updateNotifications(); if (mPendingWorkRemoteInputView != null && !mLockscreenUserManager.isAnyProfilePublicMode()) { // Expand notification panel and the notification row, then click on remote input view @@ -4889,7 +4572,7 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean hasActiveNotifications() { - return !mNotificationData.getActiveNotifications().isEmpty(); + return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } @Override @@ -5268,7 +4951,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected IStatusBarService mBarService; // all notifications - protected NotificationData mNotificationData; protected NotificationStackScrollLayout mStackScroller; protected NotificationGroupManager mGroupManager; @@ -5288,14 +4970,10 @@ public class StatusBar extends SystemUI implements DemoMode, protected boolean mDeviceInteractive; protected boolean mVisible; - protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; - protected boolean mUseHeadsUp = false; - protected boolean mDisableNotificationAlerts = false; - protected DevicePolicyManager mDevicePolicyManager; protected PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -5304,7 +4982,6 @@ public class StatusBar extends SystemUI implements DemoMode, private LockPatternUtils mLockPatternUtils; private DeviceProvisionedController mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - protected SystemServicesProxy mSystemServicesProxy; // UI-specific methods @@ -5319,8 +4996,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected DismissView mDismissView; protected EmptyShadeView mEmptyShadeView; - private final NotificationClicker mNotificationClicker = new NotificationClicker(); - protected AssistManager mAssistManager; protected boolean mVrMode; @@ -5345,13 +5020,10 @@ public class StatusBar extends SystemUI implements DemoMode, return mVrMode; } - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateNotifications(); - } - }; + @Override + public NotificationEntryManager getEntryManager() { + return mEntryManager; + } private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { @Override @@ -5377,6 +5049,121 @@ public class StatusBar extends SystemUI implements DemoMode, } }; + @Override + public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { + Notification notification = sbn.getNotification(); + final PendingIntent intent = notification.contentIntent != null + ? notification.contentIntent + : notification.fullScreenIntent; + final String notificationKey = sbn.getKey(); + + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + // TODO: Some of this code may be able to move to NotificationEntryManager. + if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { + // Release the HUN notification to the shade. + + if (isPresenterFullyCollapsed()) { + HeadsUpManager.setIsClickedNotification(row, true); + } + // + // In most cases, when FLAG_AUTO_CANCEL is set, the notification will + // become canceled shortly by NoMan, but we can't assume that. + mHeadsUpManager.releaseImmediately(notificationKey); + } + StatusBarNotification parentToCancel = null; + if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { + StatusBarNotification summarySbn = + mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification(); + if (shouldAutoCancel(summarySbn)) { + parentToCancel = summarySbn; + } + } + final StatusBarNotification parentToCancelFinal = parentToCancel; + final Runnable runnable = () -> { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + if (intent != null) { + // If we are launching a work activity and require to launch + // separate work challenge, we defer the activity action and cancel + // notification until work challenge is unlocked. + if (intent.isActivity()) { + final int userId = intent.getCreatorUserHandle().getIdentifier(); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) + && mKeyguardManager.isDeviceLocked(userId)) { + // TODO(b/28935539): should allow certain activities to + // bypass work challenge + if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(), + notificationKey)) { + // Show work challenge, do not run PendingIntent and + // remove notification + return; + } + } + } + try { + intent.send(null, 0, null, null, null, null, getActivityOptions()); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending contentIntent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + } + } + + try { + mBarService.onNotificationClick(notificationKey); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + if (parentToCancelFinal != null) { + // We have to post it to the UI thread for synchronization + mHandler.post(() -> { + Runnable removeRunnable = + () -> mEntryManager.performRemoveNotification(parentToCancelFinal); + if (isCollapsing()) { + // To avoid lags we're only performing the remove + // after the shade was collapsed + addPostCollapseAction(removeRunnable); + } else { + removeRunnable.run(); + } + }); + } + }; + + if (mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isOccluded()) { + mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); + } else { + new Thread(runnable).start(); + } + + if (!mNotificationPanel.isFullyCollapsed()) { + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, + true /* delayed */); + visibilityChanged(false); + + return true; + } else { + return false; + } + }, afterKeyguardGone); + } + protected NotificationListener mNotificationListener; protected void notifyUserAboutHiddenNotifications() { @@ -5438,14 +5225,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mLockscreenUserManager.isCurrentProfile(notificationUserId); } - protected void setNotificationShown(StatusBarNotification n) { - try { - mNotificationListener.setNotificationsShown(new String[]{n.getKey()}); - } catch (RuntimeException e) { - Log.d(TAG, "failed setNotificationsShown: ", e); - } - } - @Override public NotificationGroupManager getGroupManager() { return mGroupManager; @@ -5475,15 +5254,15 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item); - } - @Override public void toggleSplitScreen() { toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */); } + void awakenDreams() { + SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); + } + @Override public void preloadRecentApps() { int msg = MSG_PRELOAD_RECENT_APPS; @@ -5569,96 +5348,6 @@ public class StatusBar extends SystemUI implements DemoMode, } } - protected void inflateViews(Entry entry, ViewGroup parent) { - PackageManager pmUser = getPackageManagerForUser(mContext, - entry.notification.getUser().getIdentifier()); - - final StatusBarNotification sbn = entry.notification; - if (entry.row != null) { - entry.reset(); - updateNotification(entry, pmUser, sbn, entry.row); - } else { - new RowInflaterTask().inflate(mContext, parent, entry, - row -> { - bindRow(entry, pmUser, sbn, row); - updateNotification(entry, pmUser, sbn, row); - }); - } - - } - - private void bindRow(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setExpansionLogger(this, entry.notification.getKey()); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setAboveShelfChangedListener(mAboveShelfObserver); - row.setOnExpandClickListener(this); - row.setInflationCallback(this); - row.setSecureStateProvider(this::isKeyguardCurrentlySecure); - row.setLongPressListener(getNotificationLongClicker()); - mRemoteInputManager.bindRow(row); - - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (NameNotFoundException e) { - // Do nothing - } - row.setAppName(appname); - row.setOnDismissRunnable(() -> - performRemoveNotification(row.getStatusBarNotification())); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - } - - private void updateNotification(Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); - boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); - boolean isUpdate = mNotificationData.get(entry.key) != null; - boolean wasLowPriority = row.isLowPriority(); - row.setIsLowPriority(isLowPriority); - row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); - // bind the click event to the content area - mNotificationClicker.register(row, sbn); - - // Extract target SDK version. - try { - ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); - entry.targetSdk = info.targetSdkVersion; - } catch (NameNotFoundException ex) { - Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); - } - row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD - && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.autoRedacted = entry.notification.getNotification().publicVersion == null; - - entry.row = row; - entry.row.setOnActivatedListener(this); - - boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, - mNotificationData.getImportance(sbn.getKey())); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded; - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.updateNotification(entry); - } - public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { if (!isDeviceProvisioned()) return; @@ -5702,166 +5391,15 @@ public class StatusBar extends SystemUI implements DemoMode, }, afterKeyguardGone); } - - private final class NotificationClicker implements View.OnClickListener { - - @Override - public void onClick(final View v) { - if (!(v instanceof ExpandableNotificationRow)) { - Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); - return; - } - - wakeUpIfDozing(SystemClock.uptimeMillis(), v); - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getStatusBarNotification(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } - - // Check if the notification is displaying the menu, if so slide notification back - if (row.getProvider() != null && row.getProvider().isMenuVisible()) { - row.animateTranslateNotification(0); - return; - } - - Notification notification = sbn.getNotification(); - final PendingIntent intent = notification.contentIntent != null - ? notification.contentIntent - : notification.fullScreenIntent; - final String notificationKey = sbn.getKey(); - - // Mark notification for one frame. - row.setJustClicked(true); - DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); - - final boolean afterKeyguardGone = intent.isActivity() - && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), - mLockscreenUserManager.getCurrentUserId()); - dismissKeyguardThenExecute(() -> { - if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) { - // Release the HUN notification to the shade. - - if (isPresenterFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); - } - // - // In most cases, when FLAG_AUTO_CANCEL is set, the notification will - // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpManager.releaseImmediately(notificationKey); - } - StatusBarNotification parentToCancel = null; - if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { - StatusBarNotification summarySbn = - mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification(); - if (shouldAutoCancel(summarySbn)) { - parentToCancel = summarySbn; - } - } - final StatusBarNotification parentToCancelFinal = parentToCancel; - final Runnable runnable = () -> { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - if (intent != null) { - // If we are launching a work activity and require to launch - // separate work challenge, we defer the activity action and cancel - // notification until work challenge is unlocked. - if (intent.isActivity()) { - final int userId = intent.getCreatorUserHandle().getIdentifier(); - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) - && mKeyguardManager.isDeviceLocked(userId)) { - // TODO(b/28935539): should allow certain activities to - // bypass work challenge - if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(), - notificationKey)) { - // Show work challenge, do not run PendingIntent and - // remove notification - return; - } - } - } - try { - intent.send(null, 0, null, null, null, null, getActivityOptions()); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending contentIntent failed: " + e); - - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManager.hideAssist(); - } - } - - try { - mBarService.onNotificationClick(notificationKey); - } catch (RemoteException ex) { - // system process is dead if we're here. - } - if (parentToCancelFinal != null) { - // We have to post it to the UI thread for synchronization - mHandler.post(() -> { - Runnable removeRunnable = - () -> performRemoveNotification(parentToCancelFinal); - if (isCollapsing()) { - // To avoid lags we're only performing the remove - // after the shade was collapsed - addPostCollapseAction(removeRunnable); - } else { - removeRunnable.run(); - } - }); - } - }; - - if (mStatusBarKeyguardViewManager.isShowing() - && mStatusBarKeyguardViewManager.isOccluded()) { - mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - } else { - new Thread(runnable).start(); - } - - if (!mNotificationPanel.isFullyCollapsed()) { - // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, - true /* delayed */); - visibilityChanged(false); - - return true; - } else { - return false; - } - }, afterKeyguardGone); - } - - private boolean shouldAutoCancel(StatusBarNotification sbn) { - int flags = sbn.getNotification().flags; - if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { - return false; - } - if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return false; - } - return true; + private boolean shouldAutoCancel(StatusBarNotification sbn) { + int flags = sbn.getNotification().flags; + if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { + return false; } - - public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { - row.setOnClickListener(this); - } else { - row.setOnClickListener(null); - } + if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + return false; } + return true; } protected Bundle getActivityOptions() { @@ -5904,55 +5442,6 @@ public class StatusBar extends SystemUI implements DemoMode, } /** - * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService - * about the failure. - * - * WARNING: this will call back into us. Don't hold any locks. - */ - void handleNotificationError(StatusBarNotification n, String message) { - removeNotification(n.getKey(), null); - try { - mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), - n.getInitialPid(), message, n.getUserId()); - } catch (RemoteException ex) { - // The end is nigh. - } - } - - protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { - NotificationData.Entry entry = mNotificationData.remove(key, ranking); - if (entry == null) { - Log.w(TAG, "removeNotification for unknown key: " + key); - return null; - } - updateNotifications(); - Dependency.get(LeakDetector.class).trackGarbage(entry); - return entry.notification; - } - - protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) - throws InflationException { - if (DEBUG) { - Log.d(TAG, "createNotificationViews(notification=" + sbn); - } - NotificationData.Entry entry = new NotificationData.Entry(sbn); - Dependency.get(LeakDetector.class).trackInstance(entry); - entry.createIcons(mContext, sbn); - // Construct the expanded view. - inflateViews(entry, mStackScroller); - return entry; - } - - protected void addNotificationViews(Entry entry) { - if (entry == null) { - return; - } - // Add the expanded view and icon. - mNotificationData.add(entry); - updateNotifications(); - } - - /** * Updates expanded, dimmed and locked states of notification rows. */ protected void updateRowStates() { @@ -6047,163 +5536,10 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount()); } - // TODO: Move this to NotificationEntryManager once it is created. - private void updateNotificationInternal(StatusBarNotification notification, - RankingMap ranking) throws InflationException { - if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); - - final String key = notification.getKey(); - abortExistingInflation(key); - Entry entry = mNotificationData.get(key); - if (entry == null) { - return; - } - mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - mRemoteInputManager.onUpdateNotification(entry); - - if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { - mGutsManager.setKeyToRemoveOnGutsClosed(null); - Log.w(TAG, "Notification that was kept for guts was updated. " + key); - } - - Notification n = notification.getNotification(); - mNotificationData.updateRanking(ranking); - - final StatusBarNotification oldNotification = entry.notification; - entry.notification = notification; - mGroupManager.onEntryUpdated(entry, oldNotification); - - entry.updateIcons(mContext, notification); - inflateViews(entry, mStackScroller); - - mForegroundServiceController.updateNotification(notification, - mNotificationData.getImportance(key)); - - boolean shouldPeek = shouldPeek(entry, notification); - boolean alertAgain = alertAgain(entry, n); - - updateHeadsUp(key, entry, shouldPeek, alertAgain); - updateNotifications(); - - 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); - } - - if (DEBUG) { - // Is this for you? - boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); - Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); - } - - setAreThereNotifications(); - } - - @Override - public void updateNotification(StatusBarNotification notification, RankingMap ranking) { - try { - updateNotificationInternal(notification, ranking); - } catch (InflationException e) { - handleInflationException(notification, e); - } - } - protected void notifyHeadsUpGoingToSleep() { maybeEscalateHeadsUp(); } - private boolean alertAgain(Entry oldEntry, Notification newNotification) { - return oldEntry == null || !oldEntry.hasInterrupted() - || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; - } - - protected boolean shouldPeek(Entry entry) { - return shouldPeek(entry, entry.notification); - } - - protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) { - if (!mUseHeadsUp || isDeviceInVrMode()) { - if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); - return false; - } - - if (mNotificationData.shouldFilterOut(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); - return false; - } - - boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); - - if (!inUse && !isDozing()) { - if (DEBUG) { - Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); - } - return false; - } - - if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); - return false; - } - - if (entry.hasJustLaunchedFullScreenIntent()) { - if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); - return false; - } - - if (isSnoozedPackage(sbn)) { - if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); - return false; - } - - // Allow peeking for DEFAULT notifications only if we're on Ambient Display. - int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT - : NotificationManager.IMPORTANCE_HIGH; - if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { - if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); - return false; - } - - if (mIsOccluded && !isDozing()) { - boolean devicePublic = mLockscreenUserManager. - isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); - boolean userPublic = devicePublic - || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); - boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); - if (userPublic && needsRedaction) { - return false; - } - } - - if (sbn.getNotification().fullScreenIntent != null) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey()); - return false; - } else if (mDozing) { - // We never want heads up when we are dozing. - return false; - } else { - // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent - return !mStatusBarKeyguardViewManager.isShowing() - || mStatusBarKeyguardViewManager.isOccluded(); - } - } - - // Don't peek notifications that are suppressed due to group alert behavior - if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); - return false; - } - - return true; - } - /** * @return Whether the security bouncer from Keyguard is showing. */ @@ -6233,17 +5569,6 @@ public class StatusBar extends SystemUI implements DemoMode, return contextForUser.getPackageManager(); } - @Override - public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mUiOffloadThread.submit(() -> { - try { - mBarService.onNotificationExpansionChanged(key, userAction, expanded); - } catch (RemoteException e) { - // Ignore. - } - }); - } - public boolean isKeyguardSecure() { if (mStatusBarKeyguardViewManager == null) { // startKeyguard() hasn't been called yet, so we don't know. @@ -6291,20 +5616,10 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public NotificationData getNotificationData() { - return mNotificationData; - } - - @Override public Handler getHandler() { return mHandler; } - @Override - public RankingMap getLatestRankingMap() { - return mLatestRankingMap; - } - private final NotificationInfo.CheckSaveListener mCheckSaveListener = (Runnable saveImportance, StatusBarNotification sbn) -> { // If the user has security enabled, show challenge if the setting is changed. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java new file mode 100644 index 000000000000..dcd0c8393756 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java @@ -0,0 +1,254 @@ +/* + * 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.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.ViewGroup; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.UiOffloadThread; +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; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationEntryManagerTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + @Mock private NotificationPresenter mPresenter; + @Mock private ExpandableNotificationRow mRow; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private NotificationGroupManager mGroupManager; + @Mock private NotificationGutsManager mGutsManager; + @Mock private NotificationRemoteInputManager mRemoteInputManager; + @Mock private NotificationMediaManager mMediaManager; + @Mock private ForegroundServiceController mForegroundServiceController; + @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 HeadsUpManager mHeadsUpManager; + @Mock private NotificationListenerService.RankingMap mRankingMap; + @Mock private RemoteInputController mRemoteInputController; + @Mock private IStatusBarService mBarService; + + private NotificationData.Entry mEntry; + private StatusBarNotification mSbn; + private Handler mHandler; + private TestableNotificationEntryManager mEntryManager; + private CountDownLatch mCountDownLatch; + + private class TestableNotificationEntryManager extends NotificationEntryManager { + private final CountDownLatch mCountDownLatch; + + public TestableNotificationEntryManager( + NotificationLockscreenUserManager lockscreenUserManager, + NotificationGroupManager groupManager, + NotificationGutsManager gutsManager, + NotificationRemoteInputManager remoteInputManager, + NotificationMediaManager mediaManager, + ForegroundServiceController foregroundServiceController, + NotificationListener notificationListener, + MetricsLogger metricsLogger, + DeviceProvisionedController deviceProvisionedController, + UiOffloadThread uiOffloadThread, Context context, + IStatusBarService barService) { + super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager, + mediaManager, foregroundServiceController, notificationListener, metricsLogger, + deviceProvisionedController, uiOffloadThread, context); + mBarService = barService; + mCountDownLatch = new CountDownLatch(1); + mUseHeadsUp = true; + } + + @Override + public void onAsyncInflationFinished(NotificationData.Entry entry) { + super.onAsyncInflationFinished(entry); + + mCountDownLatch.countDown(); + } + + public CountDownLatch getCountDownLatch() { + return mCountDownLatch; + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new Handler(Looper.getMainLooper()); + mCountDownLatch = new CountDownLatch(1); + + when(mPresenter.getHandler()).thenReturn(mHandler); + when(mPresenter.getEntryManager()).thenReturn(mEntryManager); + 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)); + + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); + mEntry = new NotificationData.Entry(mSbn); + mEntry.expandedIcon = mock(StatusBarIconView.class); + + mEntryManager = new TestableNotificationEntryManager(mLockscreenUserManager, + mGroupManager, mGutsManager, mRemoteInputManager, mMediaManager, + mForegroundServiceController, mNotificationListener, mMetricsLogger, + mDeviceProvisionedController, mDependency.get(UiOffloadThread.class), mContext, + mBarService); + mEntryManager.setUpWithPresenter(mPresenter, mStackScroller, mCallback, + mVisualStabilityManager, mHeadsUpManager); + } + + @Test + public void testAddNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + doAnswer(invocation -> { + mCountDownLatch.countDown(); + return null; + }).when(mCallback).onBindRow(any(), any(), any(), any()); + + // Post on main thread, otherwise we will be stuck waiting here for the inflation finished + // callback forever, since it won't execute until the tests ends. + mHandler.post(() -> { + mEntryManager.addNotification(mSbn, mRankingMap); + }); + assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES)); + assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES)); + waitForIdleSync(mHandler); + + // Check that no inflation error occurred. + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt()); + + // Row inflation: + ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass( + NotificationData.Entry.class); + verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any()); + NotificationData.Entry entry = entryCaptor.getValue(); + verify(mRemoteInputManager).bindRow(entry.row); + + // Row content inflation: + verify(mCallback).onNotificationAdded(entry); + verify(mPresenter).updateNotificationViews(); + + assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry); + assertNotNull(entry.row); + } + + @Test + public void testUpdateNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + mEntryManager.getNotificationData().add(mEntry); + + mHandler.post(() -> { + mEntryManager.updateNotification(mSbn, mRankingMap); + }); + // Wait for content update. + mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES); + waitForIdleSync(mHandler); + + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + + verify(mRemoteInputManager).onUpdateNotification(mEntry); + verify(mPresenter).updateNotificationViews(); + verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt()); + verify(mCallback).onNotificationUpdated(mSbn); + assertNotNull(mEntry.row); + } + + @Test + public void testRemoveNotification() throws Exception { + com.android.systemui.util.Assert.isNotMainThread(); + + mEntry.row = mRow; + mEntryManager.getNotificationData().add(mEntry); + + mHandler.post(() -> { + mEntryManager.removeNotification(mSbn.getKey(), mRankingMap); + }); + waitForIdleSync(mHandler); + + verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(), + any(), anyInt()); + + verify(mMediaManager).onNotificationRemoved(mSbn.getKey()); + verify(mRemoteInputManager).onRemoveNotification(mEntry); + verify(mForegroundServiceController).removeNotification(mSbn); + verify(mStackScroller).cleanUpViewState(mRow); + verify(mPresenter).updateNotificationViews(); + verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn); + verify(mRow).setRemoved(); + + assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index ccc300625c8b..b71f51089fe7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -57,6 +57,7 @@ public class NotificationListenerTest extends SysuiTestCase { private Set<String> mKeysKeptForRemoteInput; private NotificationData mNotificationData; private NotificationRemoteInputManager mRemoteInputManager; + private NotificationEntryManager mEntryManager; @Before public void setUp() { @@ -65,10 +66,12 @@ public class NotificationListenerTest extends SysuiTestCase { mNotificationData = mock(NotificationData.class); mRanking = mock(NotificationListenerService.RankingMap.class); mRemoteInputManager = mock(NotificationRemoteInputManager.class); + mEntryManager = mock(NotificationEntryManager.class); mKeysKeptForRemoteInput = new HashSet<>(); when(mPresenter.getHandler()).thenReturn(mHandler); - when(mPresenter.getNotificationData()).thenReturn(mNotificationData); + when(mPresenter.getEntryManager()).thenReturn(mEntryManager); + when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); mListener = new NotificationListener(mRemoteInputManager, mContext); @@ -82,7 +85,7 @@ public class NotificationListenerTest extends SysuiTestCase { public void testNotificationAddCallsAddNotification() { mListener.onNotificationPosted(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).addNotification(mSbn, mRanking); + verify(mEntryManager).addNotification(mSbn, mRanking); } @Test @@ -98,14 +101,14 @@ public class NotificationListenerTest extends SysuiTestCase { when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn)); mListener.onNotificationPosted(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).updateNotification(mSbn, mRanking); + verify(mEntryManager).updateNotification(mSbn, mRanking); } @Test public void testNotificationRemovalCallsRemoveNotification() { mListener.onNotificationRemoved(mSbn, mRanking); waitForIdleSync(mHandler); - verify(mPresenter).removeNotification(mSbn.getKey(), mRanking); + verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking); } @Test @@ -113,6 +116,6 @@ public class NotificationListenerTest extends SysuiTestCase { mListener.onNotificationRankingUpdate(mRanking); waitForIdleSync(mHandler); // RankingMap may be modified by plugins. - verify(mPresenter).updateNotificationRanking(any()); + verify(mEntryManager).updateNotificationRanking(any()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 40459952dc95..e4368d8b2f2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -55,6 +55,7 @@ import org.junit.runner.RunWith; @TestableLooper.RunWithLooper public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationPresenter mPresenter; + private NotificationEntryManager mEntryManager; private TestNotificationLockscreenUserManager mLockscreenUserManager; private DeviceProvisionedController mDeviceProvisionedController; private int mCurrentUserId; @@ -64,27 +65,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Before public void setUp() { mUserManager = mock(UserManager.class); - mContext.addMockSystemService(UserManager.class, mUserManager); - mHandler = new Handler(Looper.getMainLooper()); + mPresenter = mock(NotificationPresenter.class); + mEntryManager = mock(NotificationEntryManager.class); mDependency.injectMockDependency(DeviceProvisionedController.class); mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class); - mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); - mDependency.injectTestDependency(NotificationLockscreenUserManager.class, - mLockscreenUserManager); + + mHandler = new Handler(Looper.getMainLooper()); + mContext.addMockSystemService(UserManager.class, mUserManager); + mCurrentUserId = ActivityManager.getCurrentUser(); when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList( new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0))); - - mPresenter = mock(NotificationPresenter.class); when(mPresenter.getHandler()).thenReturn(mHandler); + when(mPresenter.getEntryManager()).thenReturn(mEntryManager); + + mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); + mDependency.injectTestDependency(NotificationLockscreenUserManager.class, + mLockscreenUserManager); mLockscreenUserManager.setUpWithPresenter(mPresenter); - mCurrentUserId = ActivityManager.getCurrentUser(); } @Test public void testLockScreenShowNotificationsChangeUpdatesNotifications() { mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); - verify(mPresenter, times(1)).updateNotifications(); + verify(mEntryManager, times(1)).updateNotifications(); } @Test @@ -123,7 +127,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public void testSettingsObserverUpdatesNotifications() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); mLockscreenUserManager.getSettingsObserverForTest().onChange(false); - verify(mPresenter, times(1)).updateNotifications(); + verify(mEntryManager, times(1)).updateNotifications(); } @Test 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 142ce63afbd8..b0396ef5131e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java @@ -55,6 +55,7 @@ public class NotificationLoggerTest extends SysuiTestCase { private static final int TEST_UID = 0; @Mock private NotificationPresenter mPresenter; + @Mock private NotificationEntryManager mEntryManager; @Mock private NotificationListener mListener; @Mock private NotificationStackScrollLayout mStackScroller; @Mock private IStatusBarService mBarService; @@ -69,7 +70,8 @@ public class NotificationLoggerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - when(mPresenter.getNotificationData()).thenReturn(mNotificationData); + when(mPresenter.getEntryManager()).thenReturn(mEntryManager); + when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index b881c098a1a3..e943fd39cd5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -40,6 +40,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { private NotificationData.Entry mEntry; @Mock private NotificationPresenter mPresenter; + @Mock private NotificationEntryManager mEntryManager; @Mock private RemoteInputController.Delegate mDelegate; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationRemoteInputManager.Callback mCallback; @@ -53,7 +54,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mHandler = new Handler(Looper.getMainLooper()); when(mPresenter.getHandler()).thenReturn(mHandler); - when(mPresenter.getLatestRankingMap()).thenReturn(mRanking); + when(mPresenter.getEntryManager()).thenReturn(mEntryManager); + when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking); mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager, mContext); @@ -97,7 +99,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); verify(mController).removeRemoteInput(mEntry, null); - verify(mPresenter).removeNotification(mEntry.key, mRanking); + verify(mEntryManager).removeNotification(mEntry.key, mRanking); } private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { 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 073286617979..db83655058c1 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 @@ -37,6 +37,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.StatusBarManager; import android.app.trust.TrustManager; +import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Binder; @@ -61,6 +62,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.keyguard.KeyguardHostView.OnDismissAction; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; @@ -72,10 +74,16 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; 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.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.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -113,12 +121,19 @@ public class StatusBarTest extends SysuiTestCase { ArrayList<Entry> mNotificationList; FingerprintUnlockController mFingerprintUnlockController; private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private TestableNotificationEntryManager mEntryManager; @Before public void setup() throws Exception { mContext.setTheme(R.style.Theme_SystemUI_Light); mDependency.injectMockDependency(AssistManager.class); mDependency.injectMockDependency(DeviceProvisionedController.class); + mDependency.injectMockDependency(NotificationGroupManager.class); + mDependency.injectMockDependency(NotificationGutsManager.class); + mDependency.injectMockDependency(NotificationRemoteInputManager.class); + mDependency.injectMockDependency(NotificationMediaManager.class); + mDependency.injectMockDependency(ForegroundServiceController.class); + mDependency.injectMockDependency(NotificationListener.class); mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class)); CommandQueue commandQueue = mock(CommandQueue.class); when(commandQueue.asBinder()).thenReturn(new Binder()); @@ -151,11 +166,16 @@ public class StatusBarTest extends SysuiTestCase { UiOffloadThread.class)); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + + mNotificationListener = mDependency.get(NotificationListener.class); + mNotificationLogger = mDependency.get(NotificationLogger.class); + mEntryManager = new TestableNotificationEntryManager(mMetricsLogger, + mSystemServicesProxy, mPowerManager, mContext); + mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, mKeyguardIndicationController, mStackScroller, mHeadsUpManager, - mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView, - mBarService, mNotificationListener, mNotificationLogger, mScrimController, - mFingerprintUnlockController); + mPowerManager, mNotificationPanelView, mBarService, mNotificationListener, + mNotificationLogger, mEntryManager, mScrimController, mFingerprintUnlockController); mStatusBar.mContext = mContext; mStatusBar.mComponents = mContext.getComponents(); doAnswer(invocation -> { @@ -170,6 +190,8 @@ public class StatusBarTest extends SysuiTestCase { return null; }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); + mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar, + mock(VisualStabilityManager.class), mHeadsUpManager, mNotificationData); mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller); when(mStackScroller.getActivatedChild()).thenReturn(null); @@ -334,7 +356,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -355,7 +377,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -375,7 +397,7 @@ public class StatusBarTest extends SysuiTestCase { UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -394,7 +416,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test public void testShouldPeek_suppressedScreenOff_dozing() { @@ -412,7 +434,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertFalse(mStatusBar.shouldPeek(entry, sbn)); + assertFalse(mEntryManager.shouldPeek(entry, sbn)); } @Test @@ -431,7 +453,7 @@ public class StatusBarTest extends SysuiTestCase { StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n, UserHandle.of(0), null, 0); NotificationData.Entry entry = new NotificationData.Entry(sbn); - assertTrue(mStatusBar.shouldPeek(entry, sbn)); + assertTrue(mEntryManager.shouldPeek(entry, sbn)); } @@ -564,25 +586,24 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd, - PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView, + NotificationStackScrollLayout stack, HeadsUpManager hum, + PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, - NotificationLogger notificationLogger, ScrimController scrimController, + NotificationLogger notificationLogger, + TestableNotificationEntryManager entryManager, ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; mStackScroller = stack; mHeadsUpManager = hum; - mNotificationData = nd; - mUseHeadsUp = true; mPowerManager = pm; - mSystemServicesProxy = ssp; mNotificationPanel = panelView; mBarService = barService; mNotificationListener = notificationListener; mNotificationLogger = notificationLogger; mWakefulnessLifecycle = createAwakeWakefulnessLifecycle(); + mEntryManager = entryManager; mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; } @@ -606,5 +627,39 @@ public class StatusBarTest extends SysuiTestCase { public void setUserSetupForTest(boolean userSetup) { mUserSetup = userSetup; } + + } + + private class TestableNotificationEntryManager extends NotificationEntryManager { + + public TestableNotificationEntryManager(MetricsLogger metricsLogger, + SystemServicesProxy systemServicesProxy, PowerManager powerManager, + Context context) { + super(mDependency.get(NotificationLockscreenUserManager.class), + mDependency.get(NotificationGroupManager.class), + mDependency.get(NotificationGutsManager.class), + mDependency.get(NotificationRemoteInputManager.class), + mDependency.get(NotificationMediaManager.class), + mDependency.get(ForegroundServiceController.class), + mDependency.get(NotificationListener.class), + metricsLogger, + mDependency.get(DeviceProvisionedController.class), + mDependency.get(UiOffloadThread.class), + context); + mSystemServicesProxy = systemServicesProxy; + mPowerManager = powerManager; + } + + public void setUpForTest(NotificationPresenter presenter, + NotificationStackScrollLayout stackScroller, + Callback callback, + VisualStabilityManager visualStabilityManager, + HeadsUpManager headsUpManager, + NotificationData notificationData) { + super.setUpWithPresenter(presenter, stackScroller, callback, visualStabilityManager, + headsUpManager); + mNotificationData = notificationData; + mUseHeadsUp = true; + } } } |