• 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.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;
22 
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;
38 
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;
78 
79 import java.util.ArrayList;
80 
81 public class StatusBarNotificationPresenter implements NotificationPresenter,
82         ConfigurationController.ConfigurationListener,
83         NotificationRowBinderImpl.BindRowCallback {
84 
85     private final LockscreenGestureLogger mLockscreenGestureLogger =
86             Dependency.get(LockscreenGestureLogger.class);
87 
88     private static final String TAG = "StatusBarNotificationPresenter";
89 
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);
110 
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;
118 
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;
129 
130     protected boolean mVrMode;
131     private int mMaxKeyguardNotifications;
132 
StatusBarNotificationPresenter(Context context, NotificationPanelView panel, HeadsUpManagerPhone headsUp, StatusBarWindowView statusBarWindow, ViewGroup stackScroller, DozeScrimController dozeScrimController, ScrimController scrimController, ActivityLaunchAnimator activityLaunchAnimator, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationAlertingManager notificationAlertingManager, NotificationRowBinderImpl notificationRowBinder)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) {
144         mContext = context;
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));
162 
163         if (MULTIUSER_DEBUG) {
164             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
165             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
166         }
167 
168         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
169                 Context.VR_SERVICE));
170         if (vrManager != null) {
171             try {
172                 vrManager.registerListener(mVrStateCallbacks);
173             } catch (RemoteException e) {
174                 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
175             }
176         }
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));
184 
185         NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
186         Dependency.get(InitController.class).addPostInitTask(() -> {
187             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
188                 @Override
189                 public void onNotificationAdded(NotificationEntry entry) {
190                     // Recalculate the position of the sliding windows and the titles.
191                     mShadeController.updateAreThereNotifications();
192                 }
193 
194                 @Override
195                 public void onPostEntryUpdated(NotificationEntry entry) {
196                     mShadeController.updateAreThereNotifications();
197                 }
198 
199                 @Override
200                 public void onEntryRemoved(
201                         @Nullable NotificationEntry entry,
202                         NotificationVisibility visibility,
203                         boolean removedByUser) {
204                     StatusBarNotificationPresenter.this.onNotificationRemoved(
205                             entry.key, entry.notification);
206                     if (removedByUser) {
207                         maybeEndAmbientPulse();
208                     }
209                 }
210             };
211 
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);
233 
234             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
235         });
236         Dependency.get(ConfigurationController.class).addCallback(this);
237 
238         notificationAlertingManager.setHeadsUpManager(mHeadsUpManager);
239     }
240 
241     @Override
onDensityOrFontScaleChanged()242     public void onDensityOrFontScaleChanged() {
243         MessagingMessage.dropCache();
244         MessagingGroup.dropCache();
245         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
246             updateNotificationsOnDensityOrFontScaleChanged();
247         } else {
248             mReinflateNotificationsOnUserSwitched = true;
249         }
250     }
251 
252     @Override
onUiModeChanged()253     public void onUiModeChanged() {
254         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
255             updateNotificationOnUiModeChanged();
256         } else {
257             mDispatchUiModeChangeOnUserSwitched = true;
258         }
259     }
260 
261     @Override
onOverlayChanged()262     public void onOverlayChanged() {
263         onDensityOrFontScaleChanged();
264     }
265 
updateNotificationOnUiModeChanged()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();
272             if (row != null) {
273                 row.onUiModeChanged();
274             }
275         }
276     }
277 
updateNotificationsOnDensityOrFontScaleChanged()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();
285             if (exposedGuts) {
286                 mGutsManager.onDensityOrFontScaleChanged(entry);
287             }
288         }
289     }
290 
291     @Override
isCollapsing()292     public boolean isCollapsing() {
293         return mNotificationPanel.isCollapsing()
294                 || mActivityLaunchAnimator.isAnimationPending()
295                 || mActivityLaunchAnimator.isAnimationRunning();
296     }
297 
maybeEndAmbientPulse()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.
302             // Finish the pulse.
303             mDozeScrimController.pulseOutNow();
304         }
305     }
306 
307     @Override
updateNotificationViews()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;
312 
313         // Do not modify the notifications during collapse.
314         if (isCollapsing()) {
315             mShadeController.addPostCollapseAction(this::updateNotificationViews);
316             return;
317         }
318 
319         mViewHierarchyManager.updateNotificationViews();
320 
321         mNotificationPanel.updateNotificationViews();
322     }
323 
onNotificationRemoved(String key, StatusBarNotification old)324     public void onNotificationRemoved(String key, StatusBarNotification old) {
325         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
326 
327         if (old != null) {
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();
335                 }
336             }
337         }
338         mShadeController.updateAreThereNotifications();
339     }
340 
hasActiveNotifications()341     public boolean hasActiveNotifications() {
342         return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
343     }
344 
canHeadsUp(NotificationEntry entry, StatusBarNotification sbn)345     public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
346         if (mShadeController.isDozing()) {
347             return false;
348         }
349 
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) {
357                 return false;
358             }
359         }
360 
361         if (!mCommandQueue.panelsEnabled()) {
362             if (DEBUG) {
363                 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
364             }
365             return false;
366         }
367 
368         if (sbn.getNotification().fullScreenIntent != null) {
369             if (mAccessibilityManager.isTouchExplorationEnabled()) {
370                 if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
371                 return false;
372             } else {
373                 // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
374                 return !mKeyguardMonitor.isShowing()
375                         || mShadeController.isOccluded();
376             }
377         }
378         return true;
379     }
380 
381     @Override
onUserSwitched(int newUserId)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;
391         }
392         if (mDispatchUiModeChangeOnUserSwitched) {
393             updateNotificationOnUiModeChanged();
394             mDispatchUiModeChangeOnUserSwitched = false;
395         }
396         updateNotificationViews();
397         mMediaManager.clearCurrentMediaNotification();
398         mShadeController.setLockscreenUser(newUserId);
399         updateMediaMetaData(true, false);
400     }
401 
402     @Override
onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)403     public void onBindRow(NotificationEntry entry, PackageManager pmUser,
404             StatusBarNotification sbn, ExpandableNotificationRow row) {
405         row.setAboveShelfChangedListener(mAboveShelfObserver);
406         row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
407     }
408 
409     @Override
isPresenterFullyCollapsed()410     public boolean isPresenterFullyCollapsed() {
411         return mNotificationPanel.isFullyCollapsed();
412     }
413 
414     @Override
onActivated(ActivatableNotificationView view)415     public void onActivated(ActivatableNotificationView view) {
416         onActivated();
417         if (view != null) mNotificationPanel.setActivatedChild(view);
418     }
419 
onActivated()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 */);
428         }
429     }
430 
431     @Override
onActivationReset(ActivatableNotificationView view)432     public void onActivationReset(ActivatableNotificationView view) {
433         if (view == mNotificationPanel.getActivatedChild()) {
434             mNotificationPanel.setActivatedChild(null);
435             mShadeController.onActivationReset();
436         }
437     }
438 
439     @Override
updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)440     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
441         mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
442     }
443 
444     @Override
getMaxNotificationsWhileLocked(boolean recompute)445     public int getMaxNotificationsWhileLocked(boolean recompute) {
446         if (recompute) {
447             mMaxKeyguardNotifications = Math.max(1,
448                     mNotificationPanel.computeMaxKeyguardNotifications(
449                             mMaxAllowedKeyguardNotifications));
450             return mMaxKeyguardNotifications;
451         }
452         return mMaxKeyguardNotifications;
453     }
454 
455     @Override
onUpdateRowStates()456     public void onUpdateRowStates() {
457         mNotificationPanel.onUpdateRowStates();
458     }
459 
460     @Override
onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded)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());
465         }
466     }
467 
468     @Override
isDeviceInVrMode()469     public boolean isDeviceInVrMode() {
470         return mVrMode;
471     }
472 
473     @Override
isPresenterLocked()474     public boolean isPresenterLocked() {
475         return mStatusBarKeyguardViewManager.isShowing()
476                 && mStatusBarKeyguardViewManager.isSecure();
477     }
478 
onLockedNotificationImportanceChange(OnDismissAction dismissAction)479     private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
480         mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
481         mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
482                 true /* afterKeyguardGone */);
483     }
484 
485     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
486         @Override
487         public void onVrStateChanged(boolean enabled) {
488             mVrMode = enabled;
489         }
490     };
491 
492     private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
493         @Override
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();
500                     return true;
501                 });
502             } else {
503                 saveImportance.run();
504             }
505         }
506     };
507 
508     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
509         @Override
510         public void onSettingsClick(String key) {
511             try {
512                 mBarService.onNotificationSettingsViewed(key);
513             } catch (RemoteException e) {
514                 // if we're here we're dead
515             }
516         }
517     };
518 }
519