• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
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
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
13  */
14 
15 package com.android.systemui.statusbar.phone;
16 
17 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
18 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
19 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
20 import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
21 
22 import android.annotation.Nullable;
23 import android.app.KeyguardManager;
24 import android.content.Context;
25 import android.os.RemoteException;
26 import android.os.ServiceManager;
27 import android.os.SystemClock;
28 import android.service.notification.NotificationListenerService;
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.accessibility.AccessibilityManager;
36 import android.widget.TextView;
37 
38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
39 import com.android.internal.statusbar.IStatusBarService;
40 import com.android.internal.statusbar.NotificationVisibility;
41 import com.android.internal.widget.MessagingGroup;
42 import com.android.internal.widget.MessagingMessage;
43 import com.android.keyguard.KeyguardUpdateMonitor;
44 import com.android.systemui.Dependency;
45 import com.android.systemui.ForegroundServiceNotificationListener;
46 import com.android.systemui.InitController;
47 import com.android.systemui.R;
48 import com.android.systemui.plugins.ActivityStarter;
49 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
50 import com.android.systemui.plugins.statusbar.StatusBarStateController;
51 import com.android.systemui.statusbar.CommandQueue;
52 import com.android.systemui.statusbar.KeyguardIndicationController;
53 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
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.NotificationShadeWindowController;
59 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
60 import com.android.systemui.statusbar.StatusBarState;
61 import com.android.systemui.statusbar.SysuiStatusBarStateController;
62 import com.android.systemui.statusbar.notification.AboveShelfObserver;
63 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
64 import com.android.systemui.statusbar.notification.NotificationEntryListener;
65 import com.android.systemui.statusbar.notification.NotificationEntryManager;
66 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
67 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
68 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
69 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
70 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
71 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
72 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
73 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
74 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
75 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
76 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
77 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
78 import com.android.systemui.statusbar.policy.ConfigurationController;
79 import com.android.systemui.statusbar.policy.KeyguardStateController;
80 
81 import java.util.List;
82 
83 public class StatusBarNotificationPresenter implements NotificationPresenter,
84         ConfigurationController.ConfigurationListener,
85         NotificationRowBinderImpl.BindRowCallback,
86         CommandQueue.Callbacks {
87 
88     private final LockscreenGestureLogger mLockscreenGestureLogger =
89             Dependency.get(LockscreenGestureLogger.class);
90 
91     private static final String TAG = "StatusBarNotificationPresenter";
92 
93     private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
94     private final KeyguardStateController mKeyguardStateController;
95     private final NotificationViewHierarchyManager mViewHierarchyManager =
96             Dependency.get(NotificationViewHierarchyManager.class);
97     private final NotificationLockscreenUserManager mLockscreenUserManager =
98             Dependency.get(NotificationLockscreenUserManager.class);
99     private final SysuiStatusBarStateController mStatusBarStateController =
100             (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
101     private final NotificationEntryManager mEntryManager =
102             Dependency.get(NotificationEntryManager.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 
110     private final NotificationPanelViewController mNotificationPanel;
111     private final HeadsUpManagerPhone mHeadsUpManager;
112     private final AboveShelfObserver mAboveShelfObserver;
113     private final DozeScrimController mDozeScrimController;
114     private final ScrimController mScrimController;
115     private final KeyguardIndicationController mKeyguardIndicationController;
116     private final StatusBar mStatusBar;
117     private final ShadeController mShadeController;
118     private final LockscreenShadeTransitionController mShadeTransitionController;
119     private final CommandQueue mCommandQueue;
120 
121     private final AccessibilityManager mAccessibilityManager;
122     private final KeyguardManager mKeyguardManager;
123     private final NotificationShadeWindowController mNotificationShadeWindowController;
124     private final IStatusBarService mBarService;
125     private final DynamicPrivacyController mDynamicPrivacyController;
126     private boolean mReinflateNotificationsOnUserSwitched;
127     private boolean mDispatchUiModeChangeOnUserSwitched;
128     private TextView mNotificationPanelDebugText;
129 
130     protected boolean mVrMode;
131 
StatusBarNotificationPresenter(Context context, NotificationPanelViewController panel, HeadsUpManagerPhone headsUp, NotificationShadeWindowView statusBarWindow, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, ScrimController scrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, ShadeController shadeController, LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider)132     public StatusBarNotificationPresenter(Context context,
133             NotificationPanelViewController panel,
134             HeadsUpManagerPhone headsUp,
135             NotificationShadeWindowView statusBarWindow,
136             NotificationStackScrollLayoutController stackScrollerController,
137             DozeScrimController dozeScrimController,
138             ScrimController scrimController,
139             NotificationShadeWindowController notificationShadeWindowController,
140             DynamicPrivacyController dynamicPrivacyController,
141             KeyguardStateController keyguardStateController,
142             KeyguardIndicationController keyguardIndicationController,
143             StatusBar statusBar,
144             ShadeController shadeController,
145             LockscreenShadeTransitionController shadeTransitionController,
146             CommandQueue commandQueue,
147             InitController initController,
148             NotificationInterruptStateProvider notificationInterruptStateProvider) {
149         mKeyguardStateController = keyguardStateController;
150         mNotificationPanel = panel;
151         mHeadsUpManager = headsUp;
152         mDynamicPrivacyController = dynamicPrivacyController;
153         mKeyguardIndicationController = keyguardIndicationController;
154         // TODO: use KeyguardStateController#isOccluded to remove this dependency
155         mStatusBar = statusBar;
156         mShadeController = shadeController;
157         mShadeTransitionController = shadeTransitionController;
158         mCommandQueue = commandQueue;
159         mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
160         mNotificationShadeWindowController = notificationShadeWindowController;
161         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
162                 R.id.notification_container_parent));
163         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
164         mDozeScrimController = dozeScrimController;
165         mScrimController = scrimController;
166         mKeyguardManager = context.getSystemService(KeyguardManager.class);
167         mBarService = IStatusBarService.Stub.asInterface(
168                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
169 
170         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
171                 Context.VR_SERVICE));
172         if (vrManager != null) {
173             try {
174                 vrManager.registerListener(mVrStateCallbacks);
175             } catch (RemoteException e) {
176                 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
177             }
178         }
179         NotificationRemoteInputManager remoteInputManager =
180                 Dependency.get(NotificationRemoteInputManager.class);
181         remoteInputManager.setUpWithCallback(
182                 Dependency.get(NotificationRemoteInputManager.Callback.class),
183                 mNotificationPanel.createRemoteInputDelegate());
184         remoteInputManager.getController().addCallback(
185                 Dependency.get(NotificationShadeWindowController.class));
186 
187         initController.addPostInitTask(() -> {
188             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
189                 @Override
190                 public void onEntryRemoved(
191                         @Nullable NotificationEntry entry,
192                         NotificationVisibility visibility,
193                         boolean removedByUser,
194                         int reason) {
195                     StatusBarNotificationPresenter.this.onNotificationRemoved(
196                             entry.getKey(), entry.getSbn(), reason);
197                     if (removedByUser) {
198                         maybeEndAmbientPulse();
199                     }
200                 }
201             };
202 
203             mKeyguardIndicationController.init();
204             mViewHierarchyManager.setUpWithPresenter(this,
205                     stackScrollerController.getNotificationListContainer());
206             mEntryManager.setUpWithPresenter(this);
207             mEntryManager.addNotificationEntryListener(notificationEntryListener);
208             mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
209             mEntryManager.addNotificationLifetimeExtender(mGutsManager);
210             mEntryManager.addNotificationLifetimeExtenders(
211                     remoteInputManager.getLifetimeExtenders());
212             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
213             mLockscreenUserManager.setUpWithPresenter(this);
214             mMediaManager.setUpWithPresenter(this);
215             mGutsManager.setUpWithPresenter(this,
216                     stackScrollerController.getNotificationListContainer(), mCheckSaveListener,
217                     mOnSettingsClickListener);
218             // ForegroundServiceNotificationListener adds its listener in its constructor
219             // but we need to request it here in order for it to be instantiated.
220             // TODO: figure out how to do this correctly once Dependency.get() is gone.
221             Dependency.get(ForegroundServiceNotificationListener.class);
222 
223             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
224         });
225         Dependency.get(ConfigurationController.class).addCallback(this);
226     }
227 
228     @Override
onDensityOrFontScaleChanged()229     public void onDensityOrFontScaleChanged() {
230         MessagingMessage.dropCache();
231         MessagingGroup.dropCache();
232         if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
233             updateNotificationsOnDensityOrFontScaleChanged();
234         } else {
235             mReinflateNotificationsOnUserSwitched = true;
236         }
237     }
238 
239     @Override
onUiModeChanged()240     public void onUiModeChanged() {
241         if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
242             updateNotificationOnUiModeChanged();
243         } else {
244             mDispatchUiModeChangeOnUserSwitched = true;
245         }
246     }
247 
248     @Override
onOverlayChanged()249     public void onOverlayChanged() {
250         onDensityOrFontScaleChanged();
251     }
252 
updateNotificationOnUiModeChanged()253     private void updateNotificationOnUiModeChanged() {
254         List<NotificationEntry> userNotifications =
255                 mEntryManager.getActiveNotificationsForCurrentUser();
256         for (int i = 0; i < userNotifications.size(); i++) {
257             NotificationEntry entry = userNotifications.get(i);
258             ExpandableNotificationRow row = entry.getRow();
259             if (row != null) {
260                 row.onUiModeChanged();
261             }
262         }
263     }
264 
updateNotificationsOnDensityOrFontScaleChanged()265     private void updateNotificationsOnDensityOrFontScaleChanged() {
266         List<NotificationEntry> userNotifications =
267                 mEntryManager.getActiveNotificationsForCurrentUser();
268         for (int i = 0; i < userNotifications.size(); i++) {
269             NotificationEntry entry = userNotifications.get(i);
270             entry.onDensityOrFontScaleChanged();
271             boolean exposedGuts = entry.areGutsExposed();
272             if (exposedGuts) {
273                 mGutsManager.onDensityOrFontScaleChanged(entry);
274             }
275         }
276     }
277 
278     @Override
isCollapsing()279     public boolean isCollapsing() {
280         return mNotificationPanel.isCollapsing()
281                 || mNotificationShadeWindowController.isLaunchingActivity();
282     }
283 
maybeEndAmbientPulse()284     private void maybeEndAmbientPulse() {
285         if (mNotificationPanel.hasPulsingNotifications() &&
286                 !mHeadsUpManager.hasNotifications()) {
287             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
288             // Finish the pulse.
289             mDozeScrimController.pulseOutNow();
290         }
291     }
292 
293     @Override
updateNotificationViews(final String reason)294     public void updateNotificationViews(final String reason) {
295         // The function updateRowStates depends on both of these being non-null, so check them here.
296         // We may be called before they are set from DeviceProvisionedController's callback.
297         if (mScrimController == null) return;
298 
299         // Do not modify the notifications during collapse.
300         if (isCollapsing()) {
301             mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
302             return;
303         }
304 
305         mViewHierarchyManager.updateNotificationViews();
306 
307         mNotificationPanel.updateNotificationViews(reason);
308     }
309 
onNotificationRemoved(String key, StatusBarNotification old, int reason)310     private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
311         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
312 
313         if (old != null) {
314             if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
315                     && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
316                 if (mStatusBarStateController.getState() == StatusBarState.SHADE
317                         && reason != NotificationListenerService.REASON_CLICK) {
318                     mCommandQueue.animateCollapsePanels();
319                 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
320                         && !isCollapsing()) {
321                     mStatusBarStateController.setState(StatusBarState.KEYGUARD);
322                 }
323             }
324         }
325     }
326 
hasActiveNotifications()327     public boolean hasActiveNotifications() {
328         return mEntryManager.hasActiveNotifications();
329     }
330 
331     @Override
onUserSwitched(int newUserId)332     public void onUserSwitched(int newUserId) {
333         // Begin old BaseStatusBar.userSwitched
334         mHeadsUpManager.setUser(newUserId);
335         // End old BaseStatusBar.userSwitched
336         if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
337         mCommandQueue.animateCollapsePanels();
338         if (mReinflateNotificationsOnUserSwitched) {
339             updateNotificationsOnDensityOrFontScaleChanged();
340             mReinflateNotificationsOnUserSwitched = false;
341         }
342         if (mDispatchUiModeChangeOnUserSwitched) {
343             updateNotificationOnUiModeChanged();
344             mDispatchUiModeChangeOnUserSwitched = false;
345         }
346         updateNotificationViews("user switched");
347         mMediaManager.clearCurrentMediaNotification();
348         mStatusBar.setLockscreenUser(newUserId);
349         updateMediaMetaData(true, false);
350     }
351 
352     @Override
onBindRow(ExpandableNotificationRow row)353     public void onBindRow(ExpandableNotificationRow row) {
354         row.setAboveShelfChangedListener(mAboveShelfObserver);
355         row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen);
356     }
357 
358     @Override
isPresenterFullyCollapsed()359     public boolean isPresenterFullyCollapsed() {
360         return mNotificationPanel.isFullyCollapsed();
361     }
362 
363     @Override
onActivated(ActivatableNotificationView view)364     public void onActivated(ActivatableNotificationView view) {
365         onActivated();
366         if (view != null) mNotificationPanel.setActivatedChild(view);
367     }
368 
onActivated()369     public void onActivated() {
370         mLockscreenGestureLogger.write(
371                 MetricsEvent.ACTION_LS_NOTE,
372                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
373         mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
374         ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
375         if (previousView != null) {
376             previousView.makeInactive(true /* animate */);
377         }
378     }
379 
380     @Override
onActivationReset(ActivatableNotificationView view)381     public void onActivationReset(ActivatableNotificationView view) {
382         if (view == mNotificationPanel.getActivatedChild()) {
383             mNotificationPanel.setActivatedChild(null);
384             mKeyguardIndicationController.hideTransientIndication();
385         }
386     }
387 
388     @Override
updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)389     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
390         mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
391     }
392 
393     @Override
onUpdateRowStates()394     public void onUpdateRowStates() {
395         mNotificationPanel.onUpdateRowStates();
396     }
397 
398     @Override
onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)399     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
400             boolean nowExpanded) {
401         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
402         mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK");
403         if (nowExpanded) {
404             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
405                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
406             } else if (clickedEntry.isSensitive()
407                     && mDynamicPrivacyController.isInLockedDownShade()) {
408                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
409                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
410                         , null /* cancelRunnable */, false /* afterKeyguardGone */);
411             }
412         }
413     }
414 
415     @Override
isDeviceInVrMode()416     public boolean isDeviceInVrMode() {
417         return mVrMode;
418     }
419 
onLockedNotificationImportanceChange(OnDismissAction dismissAction)420     private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
421         mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
422         mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
423                 true /* afterKeyguardGone */);
424     }
425 
426     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
427         @Override
428         public void onVrStateChanged(boolean enabled) {
429             mVrMode = enabled;
430         }
431     };
432 
433     private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
434         @Override
435         public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
436             // If the user has security enabled, show challenge if the setting is changed.
437             if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
438                     && mKeyguardManager.isKeyguardLocked()) {
439                 onLockedNotificationImportanceChange(() -> {
440                     saveImportance.run();
441                     return true;
442                 });
443             } else {
444                 saveImportance.run();
445             }
446         }
447     };
448 
449     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
450         @Override
451         public void onSettingsClick(String key) {
452             try {
453                 mBarService.onNotificationSettingsViewed(key);
454             } catch (RemoteException e) {
455                 // if we're here we're dead
456             }
457         }
458     };
459 
460     private final NotificationInterruptSuppressor mInterruptSuppressor =
461             new NotificationInterruptSuppressor() {
462         @Override
463         public String getName() {
464             return TAG;
465         }
466 
467         @Override
468         public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
469             final StatusBarNotification sbn = entry.getSbn();
470             if (mStatusBar.isOccluded()) {
471                 boolean devicePublic = mLockscreenUserManager
472                         .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
473                 boolean userPublic = devicePublic
474                         || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
475                 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
476                 if (userPublic && needsRedaction) {
477                     // TODO(b/135046837): we can probably relax this with dynamic privacy
478                     return true;
479                 }
480             }
481 
482             if (!mCommandQueue.panelsEnabled()) {
483                 if (DEBUG) {
484                     Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
485                 }
486                 return true;
487             }
488 
489             if (sbn.getNotification().fullScreenIntent != null) {
490                 // we don't allow head-up on the lockscreen (unless there's a
491                 // "showWhenLocked" activity currently showing)  if
492                 // the potential HUN has a fullscreen intent
493                 if (mKeyguardStateController.isShowing() && !mStatusBar.isOccluded()) {
494                     if (DEBUG) {
495                         Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen "
496                                 + sbn.getKey());
497                     }
498                     return true;
499                 }
500 
501                 if (mAccessibilityManager.isTouchExplorationEnabled()) {
502                     if (DEBUG) {
503                         Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
504                     }
505                     return true;
506                 }
507             }
508             return false;
509         }
510 
511         @Override
512         public boolean suppressAwakeInterruptions(NotificationEntry entry) {
513             return isDeviceInVrMode();
514         }
515 
516         @Override
517         public boolean suppressInterruptions(NotificationEntry entry) {
518             return mStatusBar.areNotificationAlertsDisabled();
519         }
520     };
521 }
522