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