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.CommandQueue;
53 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
54 import com.android.systemui.statusbar.NotificationMediaManager;
55 import com.android.systemui.statusbar.NotificationPresenter;
56 import com.android.systemui.statusbar.NotificationRemoteInputManager;
57 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
58 import com.android.systemui.statusbar.StatusBarState;
59 import com.android.systemui.statusbar.SysuiStatusBarStateController;
60 import com.android.systemui.statusbar.notification.AboveShelfObserver;
61 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
62 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
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 
110     private final NotificationPanelView mNotificationPanel;
111     private final HeadsUpManagerPhone mHeadsUpManager;
112     private final AboveShelfObserver mAboveShelfObserver;
113     private final DozeScrimController mDozeScrimController;
114     private final ScrimController mScrimController;
115     private final Context mContext;
116     private final CommandQueue mCommandQueue;
117 
118     private final AccessibilityManager mAccessibilityManager;
119     private final KeyguardManager mKeyguardManager;
120     private final ActivityLaunchAnimator mActivityLaunchAnimator;
121     private final int mMaxAllowedKeyguardNotifications;
122     private final IStatusBarService mBarService;
123     private final DynamicPrivacyController mDynamicPrivacyController;
124     private boolean mReinflateNotificationsOnUserSwitched;
125     private boolean mDispatchUiModeChangeOnUserSwitched;
126     private final UnlockMethodCache mUnlockMethodCache;
127     private TextView mNotificationPanelDebugText;
128 
129     protected boolean mVrMode;
130     private int mMaxKeyguardNotifications;
131 
StatusBarNotificationPresenter(Context context, NotificationPanelView panel, HeadsUpManagerPhone headsUp, StatusBarWindowView statusBarWindow, ViewGroup stackScroller, DozeScrimController dozeScrimController, ScrimController scrimController, ActivityLaunchAnimator activityLaunchAnimator, DynamicPrivacyController dynamicPrivacyController, NotificationAlertingManager notificationAlertingManager, NotificationRowBinderImpl notificationRowBinder)132     public StatusBarNotificationPresenter(Context context,
133             NotificationPanelView panel,
134             HeadsUpManagerPhone headsUp,
135             StatusBarWindowView statusBarWindow,
136             ViewGroup stackScroller,
137             DozeScrimController dozeScrimController,
138             ScrimController scrimController,
139             ActivityLaunchAnimator activityLaunchAnimator,
140             DynamicPrivacyController dynamicPrivacyController,
141             NotificationAlertingManager notificationAlertingManager,
142             NotificationRowBinderImpl notificationRowBinder) {
143         mContext = context;
144         mNotificationPanel = panel;
145         mHeadsUpManager = headsUp;
146         mDynamicPrivacyController = dynamicPrivacyController;
147         mCommandQueue = getComponent(context, CommandQueue.class);
148         mAboveShelfObserver = new AboveShelfObserver(stackScroller);
149         mActivityLaunchAnimator = activityLaunchAnimator;
150         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
151                 R.id.notification_container_parent));
152         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
153         mDozeScrimController = dozeScrimController;
154         mScrimController = scrimController;
155         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
156         mKeyguardManager = context.getSystemService(KeyguardManager.class);
157         mMaxAllowedKeyguardNotifications = context.getResources().getInteger(
158                 R.integer.keyguard_max_notification_count);
159         mBarService = IStatusBarService.Stub.asInterface(
160                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
161 
162         if (MULTIUSER_DEBUG) {
163             mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
164             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
165         }
166 
167         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
168                 Context.VR_SERVICE));
169         if (vrManager != null) {
170             try {
171                 vrManager.registerListener(mVrStateCallbacks);
172             } catch (RemoteException e) {
173                 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
174             }
175         }
176         NotificationRemoteInputManager remoteInputManager =
177                 Dependency.get(NotificationRemoteInputManager.class);
178         remoteInputManager.setUpWithCallback(
179                 Dependency.get(NotificationRemoteInputManager.Callback.class),
180                 mNotificationPanel.createRemoteInputDelegate());
181         remoteInputManager.getController().addCallback(
182                 Dependency.get(StatusBarWindowController.class));
183 
184         NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
185         Dependency.get(InitController.class).addPostInitTask(() -> {
186             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
187                 @Override
188                 public void onNotificationAdded(NotificationEntry entry) {
189                     // Recalculate the position of the sliding windows and the titles.
190                     mShadeController.updateAreThereNotifications();
191                 }
192 
193                 @Override
194                 public void onPostEntryUpdated(NotificationEntry entry) {
195                     mShadeController.updateAreThereNotifications();
196                 }
197 
198                 @Override
199                 public void onEntryRemoved(
200                         @Nullable NotificationEntry entry,
201                         NotificationVisibility visibility,
202                         boolean removedByUser) {
203                     StatusBarNotificationPresenter.this.onNotificationRemoved(
204                             entry.key, entry.notification);
205                     if (removedByUser) {
206                         maybeEndAmbientPulse();
207                     }
208                 }
209             };
210 
211             mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
212             mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager);
213             mEntryManager.addNotificationEntryListener(notificationEntryListener);
214             mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
215             mEntryManager.addNotificationLifetimeExtender(mGutsManager);
216             mEntryManager.addNotificationLifetimeExtenders(
217                     remoteInputManager.getLifetimeExtenders());
218             notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
219                     mEntryManager, this);
220             mNotificationInterruptionStateProvider.setUpWithPresenter(
221                     this, mHeadsUpManager, this::canHeadsUp);
222             mLockscreenUserManager.setUpWithPresenter(this);
223             mMediaManager.setUpWithPresenter(this);
224             mVisualStabilityManager.setUpWithPresenter(this);
225             mGutsManager.setUpWithPresenter(this,
226                     notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
227             // ForegroundServiceNotificationListener adds its listener in its constructor
228             // but we need to request it here in order for it to be instantiated.
229             // TODO: figure out how to do this correctly once Dependency.get() is gone.
230             Dependency.get(ForegroundServiceNotificationListener.class);
231 
232             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
233         });
234         Dependency.get(ConfigurationController.class).addCallback(this);
235 
236         notificationAlertingManager.setHeadsUpManager(mHeadsUpManager);
237     }
238 
239     @Override
onDensityOrFontScaleChanged()240     public void onDensityOrFontScaleChanged() {
241         MessagingMessage.dropCache();
242         MessagingGroup.dropCache();
243         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
244             updateNotificationsOnDensityOrFontScaleChanged();
245         } else {
246             mReinflateNotificationsOnUserSwitched = true;
247         }
248     }
249 
250     @Override
onUiModeChanged()251     public void onUiModeChanged() {
252         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
253             updateNotificationOnUiModeChanged();
254         } else {
255             mDispatchUiModeChangeOnUserSwitched = true;
256         }
257     }
258 
259     @Override
onOverlayChanged()260     public void onOverlayChanged() {
261         onDensityOrFontScaleChanged();
262     }
263 
updateNotificationOnUiModeChanged()264     private void updateNotificationOnUiModeChanged() {
265         ArrayList<NotificationEntry> userNotifications
266                 = mEntryManager.getNotificationData().getNotificationsForCurrentUser();
267         for (int i = 0; i < userNotifications.size(); i++) {
268             NotificationEntry entry = userNotifications.get(i);
269             ExpandableNotificationRow row = entry.getRow();
270             if (row != null) {
271                 row.onUiModeChanged();
272             }
273         }
274     }
275 
updateNotificationsOnDensityOrFontScaleChanged()276     private void updateNotificationsOnDensityOrFontScaleChanged() {
277         ArrayList<NotificationEntry> userNotifications =
278                 mEntryManager.getNotificationData().getNotificationsForCurrentUser();
279         for (int i = 0; i < userNotifications.size(); i++) {
280             NotificationEntry entry = userNotifications.get(i);
281             entry.onDensityOrFontScaleChanged();
282             boolean exposedGuts = entry.areGutsExposed();
283             if (exposedGuts) {
284                 mGutsManager.onDensityOrFontScaleChanged(entry);
285             }
286         }
287     }
288 
289     @Override
isCollapsing()290     public boolean isCollapsing() {
291         return mNotificationPanel.isCollapsing()
292                 || mActivityLaunchAnimator.isAnimationPending()
293                 || mActivityLaunchAnimator.isAnimationRunning();
294     }
295 
maybeEndAmbientPulse()296     private void maybeEndAmbientPulse() {
297         if (mNotificationPanel.hasPulsingNotifications() &&
298                 !mHeadsUpManager.hasNotifications()) {
299             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
300             // Finish the pulse.
301             mDozeScrimController.pulseOutNow();
302         }
303     }
304 
305     @Override
updateNotificationViews()306     public void updateNotificationViews() {
307         // The function updateRowStates depends on both of these being non-null, so check them here.
308         // We may be called before they are set from DeviceProvisionedController's callback.
309         if (mScrimController == null) return;
310 
311         // Do not modify the notifications during collapse.
312         if (isCollapsing()) {
313             mShadeController.addPostCollapseAction(this::updateNotificationViews);
314             return;
315         }
316 
317         mViewHierarchyManager.updateNotificationViews();
318 
319         mNotificationPanel.updateNotificationViews();
320     }
321 
onNotificationRemoved(String key, StatusBarNotification old)322     public void onNotificationRemoved(String key, StatusBarNotification old) {
323         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
324 
325         if (old != null) {
326             if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
327                     && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
328                 if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
329                     mCommandQueue.animateCollapsePanels();
330                 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
331                         && !isCollapsing()) {
332                     mShadeController.goToKeyguard();
333                 }
334             }
335         }
336         mShadeController.updateAreThereNotifications();
337     }
338 
hasActiveNotifications()339     public boolean hasActiveNotifications() {
340         return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
341     }
342 
canHeadsUp(NotificationEntry entry, StatusBarNotification sbn)343     public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
344         if (mShadeController.isOccluded()) {
345             boolean devicePublic = mLockscreenUserManager.
346                     isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
347             boolean userPublic = devicePublic
348                     || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
349             boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
350             if (userPublic && needsRedaction) {
351                 // TODO(b/135046837): we can probably relax this with dynamic privacy
352                 return false;
353             }
354         }
355 
356         if (!mCommandQueue.panelsEnabled()) {
357             if (DEBUG) {
358                 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
359             }
360             return false;
361         }
362 
363         if (sbn.getNotification().fullScreenIntent != null) {
364             if (mAccessibilityManager.isTouchExplorationEnabled()) {
365                 if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
366                 return false;
367             } else {
368                 // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
369                 return !mKeyguardMonitor.isShowing()
370                         || mShadeController.isOccluded();
371             }
372         }
373         return true;
374     }
375 
376     @Override
onUserSwitched(int newUserId)377     public void onUserSwitched(int newUserId) {
378         // Begin old BaseStatusBar.userSwitched
379         mHeadsUpManager.setUser(newUserId);
380         // End old BaseStatusBar.userSwitched
381         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
382         mCommandQueue.animateCollapsePanels();
383         if (mReinflateNotificationsOnUserSwitched) {
384             updateNotificationsOnDensityOrFontScaleChanged();
385             mReinflateNotificationsOnUserSwitched = false;
386         }
387         if (mDispatchUiModeChangeOnUserSwitched) {
388             updateNotificationOnUiModeChanged();
389             mDispatchUiModeChangeOnUserSwitched = false;
390         }
391         updateNotificationViews();
392         mMediaManager.clearCurrentMediaNotification();
393         mShadeController.setLockscreenUser(newUserId);
394         updateMediaMetaData(true, false);
395     }
396 
397     @Override
onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)398     public void onBindRow(NotificationEntry entry, PackageManager pmUser,
399             StatusBarNotification sbn, ExpandableNotificationRow row) {
400         row.setAboveShelfChangedListener(mAboveShelfObserver);
401         row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
402     }
403 
404     @Override
isPresenterFullyCollapsed()405     public boolean isPresenterFullyCollapsed() {
406         return mNotificationPanel.isFullyCollapsed();
407     }
408 
409     @Override
onActivated(ActivatableNotificationView view)410     public void onActivated(ActivatableNotificationView view) {
411         onActivated();
412         if (view != null) mNotificationPanel.setActivatedChild(view);
413     }
414 
onActivated()415     public void onActivated() {
416         mLockscreenGestureLogger.write(
417                 MetricsEvent.ACTION_LS_NOTE,
418                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
419         mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
420         ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
421         if (previousView != null) {
422             previousView.makeInactive(true /* animate */);
423         }
424     }
425 
426     @Override
onActivationReset(ActivatableNotificationView view)427     public void onActivationReset(ActivatableNotificationView view) {
428         if (view == mNotificationPanel.getActivatedChild()) {
429             mNotificationPanel.setActivatedChild(null);
430             mShadeController.onActivationReset();
431         }
432     }
433 
434     @Override
updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)435     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
436         mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
437     }
438 
439     @Override
getMaxNotificationsWhileLocked(boolean recompute)440     public int getMaxNotificationsWhileLocked(boolean recompute) {
441         if (recompute) {
442             mMaxKeyguardNotifications = Math.max(1,
443                     mNotificationPanel.computeMaxKeyguardNotifications(
444                             mMaxAllowedKeyguardNotifications));
445             return mMaxKeyguardNotifications;
446         }
447         return mMaxKeyguardNotifications;
448     }
449 
450     @Override
onUpdateRowStates()451     public void onUpdateRowStates() {
452         mNotificationPanel.onUpdateRowStates();
453     }
454 
455     @Override
onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded)456     public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) {
457         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
458         if (nowExpanded) {
459             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
460                 mShadeController.goToLockedShade(clickedEntry.getRow());
461             } else if (clickedEntry.isSensitive()
462                     && mDynamicPrivacyController.isInLockedDownShade()) {
463                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
464                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
465                         , null /* cancelRunnable */, false /* afterKeyguardGone */);
466             }
467         }
468     }
469 
470     @Override
isDeviceInVrMode()471     public boolean isDeviceInVrMode() {
472         return mVrMode;
473     }
474 
onLockedNotificationImportanceChange(OnDismissAction dismissAction)475     private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
476         mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
477         mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
478                 true /* afterKeyguardGone */);
479     }
480 
481     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
482         @Override
483         public void onVrStateChanged(boolean enabled) {
484             mVrMode = enabled;
485         }
486     };
487 
488     private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
489         @Override
490         public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
491             // If the user has security enabled, show challenge if the setting is changed.
492             if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
493                     && mKeyguardManager.isKeyguardLocked()) {
494                 onLockedNotificationImportanceChange(() -> {
495                     saveImportance.run();
496                     return true;
497                 });
498             } else {
499                 saveImportance.run();
500             }
501         }
502     };
503 
504     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
505         @Override
506         public void onSettingsClick(String key) {
507             try {
508                 mBarService.onNotificationSettingsViewed(key);
509             } catch (RemoteException e) {
510                 // if we're here we're dead
511             }
512         }
513     };
514 }
515