2 * Copyright (C) 2018 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
15 package com.android.systemui.statusbar.phone;
17 import static com.android.systemui.SysUiServiceProvider.getComponent;
18 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
19 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
20 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
21 import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
23 import android.annotation.Nullable;
24 import android.app.KeyguardManager;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.service.notification.StatusBarNotification;
30 import android.service.vr.IVrManager;
31 import android.service.vr.IVrStateCallbacks;
32 import android.util.Log;
33 import android.util.Slog;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.accessibility.AccessibilityManager;
37 import android.widget.TextView;
39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
40 import com.android.internal.statusbar.IStatusBarService;
41 import com.android.internal.statusbar.NotificationVisibility;
42 import com.android.internal.widget.MessagingGroup;
43 import com.android.internal.widget.MessagingMessage;
44 import com.android.keyguard.KeyguardUpdateMonitor;
45 import com.android.systemui.Dependency;
46 import com.android.systemui.ForegroundServiceNotificationListener;
47 import com.android.systemui.InitController;
48 import com.android.systemui.R;
49 import com.android.systemui.plugins.ActivityStarter;
50 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
51 import com.android.systemui.plugins.statusbar.StatusBarStateController;
52 import com.android.systemui.statusbar.AmbientPulseManager;
53 import com.android.systemui.statusbar.CommandQueue;
54 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
55 import com.android.systemui.statusbar.NotificationMediaManager;
56 import com.android.systemui.statusbar.NotificationPresenter;
57 import com.android.systemui.statusbar.NotificationRemoteInputManager;
58 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
59 import com.android.systemui.statusbar.StatusBarState;
60 import com.android.systemui.statusbar.SysuiStatusBarStateController;
61 import com.android.systemui.statusbar.notification.AboveShelfObserver;
62 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
63 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
64 import com.android.systemui.statusbar.notification.NotificationEntryListener;
65 import com.android.systemui.statusbar.notification.NotificationEntryManager;
66 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
67 import com.android.systemui.statusbar.notification.VisualStabilityManager;
68 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
69 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
70 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
71 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
72 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
73 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
74 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
75 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
76 import com.android.systemui.statusbar.policy.ConfigurationController;
77 import com.android.systemui.statusbar.policy.KeyguardMonitor;
79 import java.util.ArrayList;
81 public class StatusBarNotificationPresenter implements NotificationPresenter,
82 ConfigurationController.ConfigurationListener,
83 NotificationRowBinderImpl.BindRowCallback {
85 private final LockscreenGestureLogger mLockscreenGestureLogger =
86 Dependency.get(LockscreenGestureLogger.class);
88 private static final String TAG = "StatusBarNotificationPresenter";
90 private final ShadeController mShadeController = Dependency.get(ShadeController.class);
91 private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
92 private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
93 private final NotificationViewHierarchyManager mViewHierarchyManager =
94 Dependency.get(NotificationViewHierarchyManager.class);
95 private final NotificationLockscreenUserManager mLockscreenUserManager =
96 Dependency.get(NotificationLockscreenUserManager.class);
97 private final SysuiStatusBarStateController mStatusBarStateController =
98 (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
99 private final NotificationEntryManager mEntryManager =
100 Dependency.get(NotificationEntryManager.class);
101 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
102 Dependency.get(NotificationInterruptionStateProvider.class);
103 private final NotificationMediaManager mMediaManager =
104 Dependency.get(NotificationMediaManager.class);
105 private final VisualStabilityManager mVisualStabilityManager =
106 Dependency.get(VisualStabilityManager.class);
107 private final NotificationGutsManager mGutsManager =
108 Dependency.get(NotificationGutsManager.class);
109 protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
111 private final NotificationPanelView mNotificationPanel;
112 private final HeadsUpManagerPhone mHeadsUpManager;
113 private final AboveShelfObserver mAboveShelfObserver;
114 private final DozeScrimController mDozeScrimController;
115 private final ScrimController mScrimController;
116 private final Context mContext;
117 private final CommandQueue mCommandQueue;
119 private final AccessibilityManager mAccessibilityManager;
120 private final KeyguardManager mKeyguardManager;
121 private final ActivityLaunchAnimator mActivityLaunchAnimator;
122 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
123 private final int mMaxAllowedKeyguardNotifications;
124 private final IStatusBarService mBarService;
125 private boolean mReinflateNotificationsOnUserSwitched;
126 private boolean mDispatchUiModeChangeOnUserSwitched;
127 private final UnlockMethodCache mUnlockMethodCache;
128 private TextView mNotificationPanelDebugText;
130 protected boolean mVrMode;
131 private int mMaxKeyguardNotifications;
133 public StatusBarNotificationPresenter(Context context,
134 NotificationPanelView panel,
135 HeadsUpManagerPhone headsUp,
136 StatusBarWindowView statusBarWindow,
137 ViewGroup stackScroller,
138 DozeScrimController dozeScrimController,
139 ScrimController scrimController,
140 ActivityLaunchAnimator activityLaunchAnimator,
141 StatusBarKeyguardViewManager statusBarKeyguardViewManager,
142 NotificationAlertingManager notificationAlertingManager,
143 NotificationRowBinderImpl notificationRowBinder) {
145 mNotificationPanel = panel;
146 mHeadsUpManager = headsUp;
147 mCommandQueue = getComponent(context, CommandQueue.class);
148 mAboveShelfObserver = new AboveShelfObserver(stackScroller);
149 mActivityLaunchAnimator = activityLaunchAnimator;
150 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
151 mAboveShelfObserver.setListener(statusBarWindow.findViewById(
152 R.id.notification_container_parent));
153 mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
154 mDozeScrimController = dozeScrimController;
155 mScrimController = scrimController;
156 mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
157 mKeyguardManager = context.getSystemService(KeyguardManager.class);
158 mMaxAllowedKeyguardNotifications = context.getResources().getInteger(
159 R.integer.keyguard_max_notification_count);
160 mBarService = IStatusBarService.Stub.asInterface(
161 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
163 if (MULTIUSER_DEBUG) {
164 mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
165 mNotificationPanelDebugText.setVisibility(View.VISIBLE);
168 IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
169 Context.VR_SERVICE));
170 if (vrManager != null) {
172 vrManager.registerListener(mVrStateCallbacks);
173 } catch (RemoteException e) {
174 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
177 NotificationRemoteInputManager remoteInputManager =
178 Dependency.get(NotificationRemoteInputManager.class);
179 remoteInputManager.setUpWithCallback(
180 Dependency.get(NotificationRemoteInputManager.Callback.class),
181 mNotificationPanel.createRemoteInputDelegate());
182 remoteInputManager.getController().addCallback(
183 Dependency.get(StatusBarWindowController.class));
185 NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
186 Dependency.get(InitController.class).addPostInitTask(() -> {
187 NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
189 public void onNotificationAdded(NotificationEntry entry) {
190 // Recalculate the position of the sliding windows and the titles.
191 mShadeController.updateAreThereNotifications();
195 public void onPostEntryUpdated(NotificationEntry entry) {
196 mShadeController.updateAreThereNotifications();
200 public void onEntryRemoved(
201 @Nullable NotificationEntry entry,
202 NotificationVisibility visibility,
203 boolean removedByUser) {
204 StatusBarNotificationPresenter.this.onNotificationRemoved(
205 entry.key, entry.notification);
207 maybeEndAmbientPulse();
212 mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
213 mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager);
214 mEntryManager.addNotificationEntryListener(notificationEntryListener);
215 mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
216 mEntryManager.addNotificationLifetimeExtender(mAmbientPulseManager);
217 mEntryManager.addNotificationLifetimeExtender(mGutsManager);
218 mEntryManager.addNotificationLifetimeExtenders(
219 remoteInputManager.getLifetimeExtenders());
220 notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
221 mEntryManager, this);
222 mNotificationInterruptionStateProvider.setUpWithPresenter(
223 this, mHeadsUpManager, this::canHeadsUp);
224 mLockscreenUserManager.setUpWithPresenter(this);
225 mMediaManager.setUpWithPresenter(this);
226 mVisualStabilityManager.setUpWithPresenter(this);
227 mGutsManager.setUpWithPresenter(this,
228 notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
229 // ForegroundServiceNotificationListener adds its listener in its constructor
230 // but we need to request it here in order for it to be instantiated.
231 // TODO: figure out how to do this correctly once Dependency.get() is gone.
232 Dependency.get(ForegroundServiceNotificationListener.class);
234 onUserSwitched(mLockscreenUserManager.getCurrentUserId());
236 Dependency.get(ConfigurationController.class).addCallback(this);
238 notificationAlertingManager.setHeadsUpManager(mHeadsUpManager);
242 public void onDensityOrFontScaleChanged() {
243 MessagingMessage.dropCache();
244 MessagingGroup.dropCache();
245 if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
246 updateNotificationsOnDensityOrFontScaleChanged();
248 mReinflateNotificationsOnUserSwitched = true;
253 public void onUiModeChanged() {
254 if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
255 updateNotificationOnUiModeChanged();
257 mDispatchUiModeChangeOnUserSwitched = true;
262 public void onOverlayChanged() {
263 onDensityOrFontScaleChanged();
266 private void updateNotificationOnUiModeChanged() {
267 ArrayList<NotificationEntry> userNotifications
268 = mEntryManager.getNotificationData().getNotificationsForCurrentUser();
269 for (int i = 0; i < userNotifications.size(); i++) {
270 NotificationEntry entry = userNotifications.get(i);
271 ExpandableNotificationRow row = entry.getRow();
273 row.onUiModeChanged();
278 private void updateNotificationsOnDensityOrFontScaleChanged() {
279 ArrayList<NotificationEntry> userNotifications =
280 mEntryManager.getNotificationData().getNotificationsForCurrentUser();
281 for (int i = 0; i < userNotifications.size(); i++) {
282 NotificationEntry entry = userNotifications.get(i);
283 entry.onDensityOrFontScaleChanged();
284 boolean exposedGuts = entry.areGutsExposed();
286 mGutsManager.onDensityOrFontScaleChanged(entry);
292 public boolean isCollapsing() {
293 return mNotificationPanel.isCollapsing()
294 || mActivityLaunchAnimator.isAnimationPending()
295 || mActivityLaunchAnimator.isAnimationRunning();
298 private void maybeEndAmbientPulse() {
299 if (mNotificationPanel.hasPulsingNotifications() &&
300 !mAmbientPulseManager.hasNotifications()) {
301 // We were showing a pulse for a notification, but no notifications are pulsing anymore.
303 mDozeScrimController.pulseOutNow();
308 public void updateNotificationViews() {
309 // The function updateRowStates depends on both of these being non-null, so check them here.
310 // We may be called before they are set from DeviceProvisionedController's callback.
311 if (mScrimController == null) return;
313 // Do not modify the notifications during collapse.
314 if (isCollapsing()) {
315 mShadeController.addPostCollapseAction(this::updateNotificationViews);
319 mViewHierarchyManager.updateNotificationViews();
321 mNotificationPanel.updateNotificationViews();
324 public void onNotificationRemoved(String key, StatusBarNotification old) {
325 if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
328 if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
329 && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
330 if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
331 mCommandQueue.animateCollapsePanels();
332 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
333 && !isCollapsing()) {
334 mShadeController.goToKeyguard();
338 mShadeController.updateAreThereNotifications();
341 public boolean hasActiveNotifications() {
342 return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
345 public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
346 if (mShadeController.isDozing()) {
350 if (mShadeController.isOccluded()) {
351 boolean devicePublic = mLockscreenUserManager.
352 isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
353 boolean userPublic = devicePublic
354 || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
355 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
356 if (userPublic && needsRedaction) {
361 if (!mCommandQueue.panelsEnabled()) {
363 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
368 if (sbn.getNotification().fullScreenIntent != null) {
369 if (mAccessibilityManager.isTouchExplorationEnabled()) {
370 if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
373 // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
374 return !mKeyguardMonitor.isShowing()
375 || mShadeController.isOccluded();
382 public void onUserSwitched(int newUserId) {
383 // Begin old BaseStatusBar.userSwitched
384 mHeadsUpManager.setUser(newUserId);
385 // End old BaseStatusBar.userSwitched
386 if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
387 mCommandQueue.animateCollapsePanels();
388 if (mReinflateNotificationsOnUserSwitched) {
389 updateNotificationsOnDensityOrFontScaleChanged();
390 mReinflateNotificationsOnUserSwitched = false;
392 if (mDispatchUiModeChangeOnUserSwitched) {
393 updateNotificationOnUiModeChanged();
394 mDispatchUiModeChangeOnUserSwitched = false;
396 updateNotificationViews();
397 mMediaManager.clearCurrentMediaNotification();
398 mShadeController.setLockscreenUser(newUserId);
399 updateMediaMetaData(true, false);
403 public void onBindRow(NotificationEntry entry, PackageManager pmUser,
404 StatusBarNotification sbn, ExpandableNotificationRow row) {
405 row.setAboveShelfChangedListener(mAboveShelfObserver);
406 row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
410 public boolean isPresenterFullyCollapsed() {
411 return mNotificationPanel.isFullyCollapsed();
415 public void onActivated(ActivatableNotificationView view) {
417 if (view != null) mNotificationPanel.setActivatedChild(view);
420 public void onActivated() {
421 mLockscreenGestureLogger.write(
422 MetricsEvent.ACTION_LS_NOTE,
423 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
424 mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
425 ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
426 if (previousView != null) {
427 previousView.makeInactive(true /* animate */);
432 public void onActivationReset(ActivatableNotificationView view) {
433 if (view == mNotificationPanel.getActivatedChild()) {
434 mNotificationPanel.setActivatedChild(null);
435 mShadeController.onActivationReset();
440 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
441 mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
445 public int getMaxNotificationsWhileLocked(boolean recompute) {
447 mMaxKeyguardNotifications = Math.max(1,
448 mNotificationPanel.computeMaxKeyguardNotifications(
449 mMaxAllowedKeyguardNotifications));
450 return mMaxKeyguardNotifications;
452 return mMaxKeyguardNotifications;
456 public void onUpdateRowStates() {
457 mNotificationPanel.onUpdateRowStates();
461 public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) {
462 mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
463 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) {
464 mShadeController.goToLockedShade(clickedEntry.getRow());
469 public boolean isDeviceInVrMode() {
474 public boolean isPresenterLocked() {
475 return mStatusBarKeyguardViewManager.isShowing()
476 && mStatusBarKeyguardViewManager.isSecure();
479 private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
480 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
481 mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
482 true /* afterKeyguardGone */);
485 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
487 public void onVrStateChanged(boolean enabled) {
492 private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
494 public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
495 // If the user has security enabled, show challenge if the setting is changed.
496 if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
497 && mKeyguardManager.isKeyguardLocked()) {
498 onLockedNotificationImportanceChange(() -> {
499 saveImportance.run();
503 saveImportance.run();
508 private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
510 public void onSettingsClick(String key) {
512 mBarService.onNotificationSettingsViewed(key);
513 } catch (RemoteException e) {
514 // if we're here we're dead