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.notification.interruption.VisualInterruptionType.BUBBLE;
18 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
19 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
20 import static com.android.systemui.statusbar.phone.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED;
21 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
22 
23 import android.app.KeyguardManager;
24 import android.content.Context;
25 import android.os.PowerManager;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.service.notification.StatusBarNotification;
29 import android.service.vr.IVrManager;
30 import android.service.vr.IVrStateCallbacks;
31 import android.util.Log;
32 import android.util.Slog;
33 import android.view.View;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.internal.statusbar.IStatusBarService;
38 import com.android.systemui.InitController;
39 import com.android.systemui.dagger.SysUISingleton;
40 import com.android.systemui.plugins.ActivityStarter;
41 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
42 import com.android.systemui.power.domain.interactor.PowerInteractor;
43 import com.android.systemui.res.R;
44 import com.android.systemui.shade.NotificationShadeWindowView;
45 import com.android.systemui.shade.QuickSettingsController;
46 import com.android.systemui.shade.ShadeViewController;
47 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
48 import com.android.systemui.statusbar.CommandQueue;
49 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
50 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
51 import com.android.systemui.statusbar.NotificationMediaManager;
52 import com.android.systemui.statusbar.NotificationPresenter;
53 import com.android.systemui.statusbar.NotificationRemoteInputManager;
54 import com.android.systemui.statusbar.NotificationShadeWindowController;
55 import com.android.systemui.statusbar.StatusBarState;
56 import com.android.systemui.statusbar.SysuiStatusBarStateController;
57 import com.android.systemui.statusbar.notification.AboveShelfObserver;
58 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
59 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
60 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
61 import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
62 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
63 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
64 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
65 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter;
66 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
67 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
68 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
69 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
70 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
71 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
72 import com.android.systemui.statusbar.policy.HeadsUpManager;
73 import com.android.systemui.statusbar.policy.KeyguardStateController;
74 
75 import java.util.Set;
76 
77 import javax.inject.Inject;
78 
79 @SysUISingleton
80 class StatusBarNotificationPresenter implements NotificationPresenter, CommandQueue.Callbacks {
81     private static final String TAG = "StatusBarNotificationPresenter";
82 
83     private final ActivityStarter mActivityStarter;
84     private final KeyguardStateController mKeyguardStateController;
85     private final NotificationLockscreenUserManager mLockscreenUserManager;
86     private final SysuiStatusBarStateController mStatusBarStateController;
87     private final NotifShadeEventSource mNotifShadeEventSource;
88     private final NotificationMediaManager mMediaManager;
89     private final NotificationGutsManager mGutsManager;
90     private final ShadeViewController mNotificationPanel;
91     private final PanelExpansionInteractor mPanelExpansionInteractor;
92     private final HeadsUpManager mHeadsUpManager;
93     private final AboveShelfObserver mAboveShelfObserver;
94     private final DozeScrimController mDozeScrimController;
95     private final NotificationAlertsInteractor mNotificationAlertsInteractor;
96     private final NotificationStackScrollLayoutController mNsslController;
97     private final LockscreenShadeTransitionController mShadeTransitionController;
98     private final PowerInteractor mPowerInteractor;
99     private final CommandQueue mCommandQueue;
100     private final KeyguardManager mKeyguardManager;
101     private final NotificationShadeWindowController mNotificationShadeWindowController;
102     private final IStatusBarService mBarService;
103     private final DynamicPrivacyController mDynamicPrivacyController;
104     private final NotificationListContainer mNotifListContainer;
105     private final QuickSettingsController mQsController;
106 
107     protected boolean mVrMode;
108 
109     @Inject
StatusBarNotificationPresenter( Context context, ShadeViewController panel, PanelExpansionInteractor panelExpansionInteractor, QuickSettingsController quickSettingsController, HeadsUpManager headsUp, NotificationShadeWindowView statusBarWindow, ActivityStarter activityStarter, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, NotificationAlertsInteractor notificationAlertsInteractor, LockscreenShadeTransitionController shadeTransitionController, PowerInteractor powerInteractor, CommandQueue commandQueue, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, NotifShadeEventSource notifShadeEventSource, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, InitController initController, VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, NotificationRemoteInputManager remoteInputManager, NotificationRemoteInputManager.Callback remoteInputManagerCallback, NotificationListContainer notificationListContainer)110     StatusBarNotificationPresenter(
111             Context context,
112             ShadeViewController panel,
113             PanelExpansionInteractor panelExpansionInteractor,
114             QuickSettingsController quickSettingsController,
115             HeadsUpManager headsUp,
116             NotificationShadeWindowView statusBarWindow,
117             ActivityStarter activityStarter,
118             NotificationStackScrollLayoutController stackScrollerController,
119             DozeScrimController dozeScrimController,
120             NotificationShadeWindowController notificationShadeWindowController,
121             DynamicPrivacyController dynamicPrivacyController,
122             KeyguardStateController keyguardStateController,
123             NotificationAlertsInteractor notificationAlertsInteractor,
124             LockscreenShadeTransitionController shadeTransitionController,
125             PowerInteractor powerInteractor,
126             CommandQueue commandQueue,
127             NotificationLockscreenUserManager lockscreenUserManager,
128             SysuiStatusBarStateController sysuiStatusBarStateController,
129             NotifShadeEventSource notifShadeEventSource,
130             NotificationMediaManager notificationMediaManager,
131             NotificationGutsManager notificationGutsManager,
132             InitController initController,
133             VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
134             NotificationRemoteInputManager remoteInputManager,
135             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
136             NotificationListContainer notificationListContainer) {
137         mActivityStarter = activityStarter;
138         mKeyguardStateController = keyguardStateController;
139         mNotificationPanel = panel;
140         mPanelExpansionInteractor = panelExpansionInteractor;
141         mQsController = quickSettingsController;
142         mHeadsUpManager = headsUp;
143         mDynamicPrivacyController = dynamicPrivacyController;
144         mNotificationAlertsInteractor = notificationAlertsInteractor;
145         mNsslController = stackScrollerController;
146         mShadeTransitionController = shadeTransitionController;
147         mPowerInteractor = powerInteractor;
148         mCommandQueue = commandQueue;
149         mLockscreenUserManager = lockscreenUserManager;
150         mStatusBarStateController = sysuiStatusBarStateController;
151         mNotifShadeEventSource = notifShadeEventSource;
152         mMediaManager = notificationMediaManager;
153         mGutsManager = notificationGutsManager;
154         mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
155         mNotificationShadeWindowController = notificationShadeWindowController;
156         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
157                 R.id.notification_container_parent));
158         mDozeScrimController = dozeScrimController;
159         mKeyguardManager = context.getSystemService(KeyguardManager.class);
160         mBarService = IStatusBarService.Stub.asInterface(
161                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
162         mNotifListContainer = notificationListContainer;
163 
164         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
165                 Context.VR_SERVICE));
166         if (vrManager != null) {
167             try {
168                 vrManager.registerListener(mVrStateCallbacks);
169             } catch (RemoteException e) {
170                 Slog.e(TAG, "Failed to register VR mode state listener: " + e);
171             }
172         }
173         remoteInputManager.setUpWithCallback(
174                 remoteInputManagerCallback,
175                 mNsslController.createDelegate());
176 
177         initController.addPostInitTask(() -> {
178             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
179             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
180             if (VisualInterruptionRefactor.isEnabled()) {
181                 visualInterruptionDecisionProvider.addCondition(mAlertsDisabledCondition);
182                 visualInterruptionDecisionProvider.addCondition(mVrModeCondition);
183                 visualInterruptionDecisionProvider.addFilter(mNeedsRedactionFilter);
184                 visualInterruptionDecisionProvider.addCondition(mPanelsDisabledCondition);
185             } else {
186                 visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor);
187             }
188             mLockscreenUserManager.setUpWithPresenter(this);
189             mGutsManager.setUpWithPresenter(
190                     this, mNotifListContainer, mOnSettingsClickListener);
191 
192             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
193         });
194     }
195 
196     /** Called when the shade has been emptied to attempt to close the shade */
maybeClosePanelForShadeEmptied()197     private void maybeClosePanelForShadeEmptied() {
198         if (CLOSE_PANEL_WHEN_EMPTIED
199                 && !mPanelExpansionInteractor.isTracking()
200                 && !mQsController.getExpanded()
201                 && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
202                 && !isCollapsing()) {
203             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
204         }
205     }
206 
207     @Override
isCollapsing()208     public boolean isCollapsing() {
209         return mPanelExpansionInteractor.isCollapsing()
210                 || mNotificationShadeWindowController.isLaunchingActivity();
211     }
212 
maybeEndAmbientPulse()213     private void maybeEndAmbientPulse() {
214         if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
215                 && !mHeadsUpManager.hasNotifications()) {
216             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
217             // Finish the pulse.
218             mDozeScrimController.pulseOutNow();
219         }
220     }
221 
222     @Override
onUserSwitched(int newUserId)223     public void onUserSwitched(int newUserId) {
224         // Begin old BaseStatusBar.userSwitched
225         mHeadsUpManager.setUser(newUserId);
226         // End old BaseStatusBar.userSwitched
227         mCommandQueue.animateCollapsePanels();
228         mMediaManager.clearCurrentMediaNotification();
229     }
230 
231     @Override
onBindRow(ExpandableNotificationRow row)232     public void onBindRow(ExpandableNotificationRow row) {
233         row.setAboveShelfChangedListener(mAboveShelfObserver);
234         row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen);
235     }
236 
237     @Override
isPresenterFullyCollapsed()238     public boolean isPresenterFullyCollapsed() {
239         return mPanelExpansionInteractor.isFullyCollapsed();
240     }
241 
242     @Override
onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)243     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
244             boolean nowExpanded) {
245         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
246         mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
247         if (nowExpanded) {
248             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
249                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
250             } else if (clickedEntry.isSensitive().getValue()
251                     && mDynamicPrivacyController.isInLockedDownShade()) {
252                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
253                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
254                         , null /* cancelRunnable */, false /* afterKeyguardGone */);
255             }
256         }
257     }
258 
259     @Override
isDeviceInVrMode()260     public boolean isDeviceInVrMode() {
261         return mVrMode;
262     }
263 
onLockedNotificationImportanceChange(OnDismissAction dismissAction)264     private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
265         mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
266         mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
267                 true /* afterKeyguardGone */);
268     }
269 
270     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
271         @Override
272         public void onVrStateChanged(boolean enabled) {
273             mVrMode = enabled;
274         }
275     };
276 
277     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
278         @Override
279         public void onSettingsClick(String key) {
280             try {
281                 mBarService.onNotificationSettingsViewed(key);
282             } catch (RemoteException e) {
283                 // if we're here we're dead
284             }
285         }
286     };
287 
288     private final NotificationInterruptSuppressor mInterruptSuppressor =
289             new NotificationInterruptSuppressor() {
290         @Override
291         public String getName() {
292             return TAG;
293         }
294 
295         @Override
296         public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
297             final StatusBarNotification sbn = entry.getSbn();
298             if (mKeyguardStateController.isOccluded()) {
299                 boolean devicePublic = mLockscreenUserManager
300                         .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
301                 boolean userPublic = devicePublic
302                         || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
303                 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
304                 if (userPublic && needsRedaction) {
305                     // TODO(b/135046837): we can probably relax this with dynamic privacy
306                     return true;
307                 }
308             }
309 
310             if (!mCommandQueue.panelsEnabled()) {
311                 if (DEBUG) {
312                     Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
313                 }
314                 return true;
315             }
316 
317             return false;
318         }
319 
320         @Override
321         public boolean suppressAwakeInterruptions(NotificationEntry entry) {
322             return isDeviceInVrMode();
323         }
324 
325         @Override
326         public boolean suppressInterruptions(NotificationEntry entry) {
327             return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
328         }
329     };
330 
331     private final VisualInterruptionCondition mAlertsDisabledCondition =
332             new VisualInterruptionCondition(Set.of(PEEK, PULSE, BUBBLE),
333                     "notification alerts disabled") {
334                 @Override
335                 public boolean shouldSuppress() {
336                     return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
337                 }
338             };
339 
340     private final VisualInterruptionCondition mVrModeCondition =
341             new VisualInterruptionCondition(Set.of(PEEK, BUBBLE), "device is in VR mode") {
342                 @Override
343                 public boolean shouldSuppress() {
344                     return isDeviceInVrMode();
345                 }
346             };
347 
348     private final VisualInterruptionFilter mNeedsRedactionFilter =
349             new VisualInterruptionFilter(Set.of(PEEK), "needs redaction on public lockscreen") {
350                 @Override
351                 public boolean shouldSuppress(@NonNull NotificationEntry entry) {
352                     if (!mKeyguardStateController.isOccluded()) {
353                         return false;
354                     }
355 
356                     if (!mLockscreenUserManager.needsRedaction(entry)) {
357                         return false;
358                     }
359 
360                     final int currentUserId = mLockscreenUserManager.getCurrentUserId();
361                     final boolean currentUserPublic = mLockscreenUserManager.isLockscreenPublicMode(
362                             currentUserId);
363 
364                     final int notificationUserId = entry.getSbn().getUserId();
365                     final boolean notificationUserPublic =
366                             mLockscreenUserManager.isLockscreenPublicMode(notificationUserId);
367 
368                     // TODO(b/135046837): we can probably relax this with dynamic privacy
369                     return currentUserPublic || notificationUserPublic;
370                 }
371             };
372 
373     private final VisualInterruptionCondition mPanelsDisabledCondition =
374             new VisualInterruptionCondition(Set.of(PEEK), "disabled panel") {
375                 @Override
376                 public boolean shouldSuppress() {
377                     return !mCommandQueue.panelsEnabled();
378                 }
379             };
380 }
381