• 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.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED;
18 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
19 import static com.android.systemui.statusbar.phone.CentralSurfaces.MULTIUSER_DEBUG;
20 
21 import android.app.KeyguardManager;
22 import android.content.Context;
23 import android.os.PowerManager;
24 import android.os.RemoteException;
25 import android.os.ServiceManager;
26 import android.os.SystemClock;
27 import android.service.notification.StatusBarNotification;
28 import android.service.vr.IVrManager;
29 import android.service.vr.IVrStateCallbacks;
30 import android.util.Log;
31 import android.util.Slog;
32 import android.view.View;
33 import android.view.accessibility.AccessibilityManager;
34 
35 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
36 import com.android.internal.statusbar.IStatusBarService;
37 import com.android.systemui.InitController;
38 import com.android.systemui.R;
39 import com.android.systemui.plugins.ActivityStarter;
40 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
41 import com.android.systemui.shade.NotificationPanelViewController;
42 import com.android.systemui.shade.NotificationShadeWindowView;
43 import com.android.systemui.shade.QuickSettingsController;
44 import com.android.systemui.statusbar.CommandQueue;
45 import com.android.systemui.statusbar.KeyguardIndicationController;
46 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
47 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
48 import com.android.systemui.statusbar.NotificationMediaManager;
49 import com.android.systemui.statusbar.NotificationPresenter;
50 import com.android.systemui.statusbar.NotificationRemoteInputManager;
51 import com.android.systemui.statusbar.NotificationShadeWindowController;
52 import com.android.systemui.statusbar.StatusBarState;
53 import com.android.systemui.statusbar.SysuiStatusBarStateController;
54 import com.android.systemui.statusbar.notification.AboveShelfObserver;
55 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
56 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
57 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
58 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
59 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
60 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
61 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
62 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
63 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
64 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
65 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
66 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
67 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
68 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
69 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
70 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
71 import com.android.systemui.statusbar.policy.KeyguardStateController;
72 
73 import javax.inject.Inject;
74 
75 @CentralSurfacesComponent.CentralSurfacesScope
76 class StatusBarNotificationPresenter implements NotificationPresenter,
77         NotificationRowBinderImpl.BindRowCallback,
78         CommandQueue.Callbacks {
79     private static final String TAG = "StatusBarNotificationPresenter";
80 
81     private final ActivityStarter mActivityStarter;
82     private final KeyguardStateController mKeyguardStateController;
83     private final NotificationLockscreenUserManager mLockscreenUserManager;
84     private final SysuiStatusBarStateController mStatusBarStateController;
85     private final NotifShadeEventSource mNotifShadeEventSource;
86     private final NotificationMediaManager mMediaManager;
87     private final NotificationGutsManager mGutsManager;
88     private final LockscreenGestureLogger mLockscreenGestureLogger;
89 
90     private final NotificationPanelViewController mNotificationPanel;
91     private final HeadsUpManagerPhone mHeadsUpManager;
92     private final AboveShelfObserver mAboveShelfObserver;
93     private final DozeScrimController mDozeScrimController;
94     private final KeyguardIndicationController mKeyguardIndicationController;
95     private final CentralSurfaces mCentralSurfaces;
96     private final LockscreenShadeTransitionController mShadeTransitionController;
97     private final CommandQueue mCommandQueue;
98 
99     private final AccessibilityManager mAccessibilityManager;
100     private final KeyguardManager mKeyguardManager;
101     private final NotificationShadeWindowController mNotificationShadeWindowController;
102     private final NotifPipelineFlags mNotifPipelineFlags;
103     private final IStatusBarService mBarService;
104     private final DynamicPrivacyController mDynamicPrivacyController;
105     private final NotificationListContainer mNotifListContainer;
106     private final QuickSettingsController mQsController;
107 
108     protected boolean mVrMode;
109 
110     @Inject
StatusBarNotificationPresenter( Context context, NotificationPanelViewController panel, QuickSettingsController quickSettingsController, HeadsUpManagerPhone headsUp, NotificationShadeWindowView statusBarWindow, ActivityStarter activityStarter, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, CentralSurfaces centralSurfaces, LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, NotifShadeEventSource notifShadeEventSource, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, LockscreenGestureLogger lockscreenGestureLogger, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager, NotifPipelineFlags notifPipelineFlags, NotificationRemoteInputManager.Callback remoteInputManagerCallback, NotificationListContainer notificationListContainer)111     StatusBarNotificationPresenter(
112             Context context,
113             NotificationPanelViewController panel,
114             QuickSettingsController quickSettingsController,
115             HeadsUpManagerPhone headsUp,
116             NotificationShadeWindowView statusBarWindow,
117             ActivityStarter activityStarter,
118             NotificationStackScrollLayoutController stackScrollerController,
119             DozeScrimController dozeScrimController,
120             NotificationShadeWindowController notificationShadeWindowController,
121             DynamicPrivacyController dynamicPrivacyController,
122             KeyguardStateController keyguardStateController,
123             KeyguardIndicationController keyguardIndicationController,
124             CentralSurfaces centralSurfaces,
125             LockscreenShadeTransitionController shadeTransitionController,
126             CommandQueue commandQueue,
127             NotificationLockscreenUserManager lockscreenUserManager,
128             SysuiStatusBarStateController sysuiStatusBarStateController,
129             NotifShadeEventSource notifShadeEventSource,
130             NotificationMediaManager notificationMediaManager,
131             NotificationGutsManager notificationGutsManager,
132             LockscreenGestureLogger lockscreenGestureLogger,
133             InitController initController,
134             NotificationInterruptStateProvider notificationInterruptStateProvider,
135             NotificationRemoteInputManager remoteInputManager,
136             NotifPipelineFlags notifPipelineFlags,
137             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
138             NotificationListContainer notificationListContainer) {
139         mActivityStarter = activityStarter;
140         mKeyguardStateController = keyguardStateController;
141         mNotificationPanel = panel;
142         mQsController = quickSettingsController;
143         mHeadsUpManager = headsUp;
144         mDynamicPrivacyController = dynamicPrivacyController;
145         mKeyguardIndicationController = keyguardIndicationController;
146         // TODO: use KeyguardStateController#isOccluded to remove this dependency
147         mCentralSurfaces = centralSurfaces;
148         mShadeTransitionController = shadeTransitionController;
149         mCommandQueue = commandQueue;
150         mLockscreenUserManager = lockscreenUserManager;
151         mStatusBarStateController = sysuiStatusBarStateController;
152         mNotifShadeEventSource = notifShadeEventSource;
153         mMediaManager = notificationMediaManager;
154         mGutsManager = notificationGutsManager;
155         mLockscreenGestureLogger = lockscreenGestureLogger;
156         mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
157         mNotificationShadeWindowController = notificationShadeWindowController;
158         mNotifPipelineFlags = notifPipelineFlags;
159         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
160                 R.id.notification_container_parent));
161         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
162         mDozeScrimController = dozeScrimController;
163         mKeyguardManager = context.getSystemService(KeyguardManager.class);
164         mBarService = IStatusBarService.Stub.asInterface(
165                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
166         mNotifListContainer = notificationListContainer;
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         remoteInputManager.setUpWithCallback(
178                 remoteInputManagerCallback,
179                 mNotificationPanel.createRemoteInputDelegate());
180 
181         initController.addPostInitTask(() -> {
182             mKeyguardIndicationController.init();
183             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
184             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
185             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
186             mLockscreenUserManager.setUpWithPresenter(this);
187             mGutsManager.setUpWithPresenter(
188                     this, mNotifListContainer, mOnSettingsClickListener);
189 
190             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
191         });
192     }
193 
194     /** Called when the shade has been emptied to attempt to close the shade */
maybeClosePanelForShadeEmptied()195     private void maybeClosePanelForShadeEmptied() {
196         if (CLOSE_PANEL_WHEN_EMPTIED
197                 && !mNotificationPanel.isTracking()
198                 && !mQsController.getExpanded()
199                 && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
200                 && !isCollapsing()) {
201             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
202         }
203     }
204 
205     @Override
isCollapsing()206     public boolean isCollapsing() {
207         return mNotificationPanel.isCollapsing()
208                 || mNotificationShadeWindowController.isLaunchingActivity();
209     }
210 
maybeEndAmbientPulse()211     private void maybeEndAmbientPulse() {
212         if (mNotificationPanel.hasPulsingNotifications() &&
213                 !mHeadsUpManager.hasNotifications()) {
214             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
215             // Finish the pulse.
216             mDozeScrimController.pulseOutNow();
217         }
218     }
219 
220     @Override
onUserSwitched(int newUserId)221     public void onUserSwitched(int newUserId) {
222         // Begin old BaseStatusBar.userSwitched
223         mHeadsUpManager.setUser(newUserId);
224         // End old BaseStatusBar.userSwitched
225         if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
226         mCommandQueue.animateCollapsePanels();
227         mMediaManager.clearCurrentMediaNotification();
228         mCentralSurfaces.setLockscreenUser(newUserId);
229         updateMediaMetaData(true, false);
230     }
231 
232     @Override
onBindRow(ExpandableNotificationRow row)233     public void onBindRow(ExpandableNotificationRow row) {
234         row.setAboveShelfChangedListener(mAboveShelfObserver);
235         row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen);
236     }
237 
238     @Override
isPresenterFullyCollapsed()239     public boolean isPresenterFullyCollapsed() {
240         return mNotificationPanel.isFullyCollapsed();
241     }
242 
243     @Override
onActivated(ActivatableNotificationView view)244     public void onActivated(ActivatableNotificationView view) {
245         onActivated();
246         if (view != null) mNotificationPanel.setActivatedChild(view);
247     }
248 
onActivated()249     public void onActivated() {
250         mLockscreenGestureLogger.write(
251                 MetricsEvent.ACTION_LS_NOTE,
252                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
253         mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
254         ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
255         if (previousView != null) {
256             previousView.makeInactive(true /* animate */);
257         }
258     }
259 
260     @Override
onActivationReset(ActivatableNotificationView view)261     public void onActivationReset(ActivatableNotificationView view) {
262         if (view == mNotificationPanel.getActivatedChild()) {
263             mNotificationPanel.setActivatedChild(null);
264             mKeyguardIndicationController.hideTransientIndication();
265         }
266     }
267 
268     @Override
updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)269     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
270         mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
271     }
272 
273     @Override
onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)274     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
275             boolean nowExpanded) {
276         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
277         mCentralSurfaces.wakeUpIfDozing(
278                 SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK",
279                 PowerManager.WAKE_REASON_GESTURE);
280         if (nowExpanded) {
281             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
282                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
283             } else if (clickedEntry.isSensitive()
284                     && mDynamicPrivacyController.isInLockedDownShade()) {
285                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
286                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
287                         , null /* cancelRunnable */, false /* afterKeyguardGone */);
288             }
289         }
290     }
291 
292     @Override
isDeviceInVrMode()293     public boolean isDeviceInVrMode() {
294         return mVrMode;
295     }
296 
onLockedNotificationImportanceChange(OnDismissAction dismissAction)297     private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
298         mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
299         mActivityStarter.dismissKeyguardThenExecute(dismissAction, null,
300                 true /* afterKeyguardGone */);
301     }
302 
303     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
304         @Override
305         public void onVrStateChanged(boolean enabled) {
306             mVrMode = enabled;
307         }
308     };
309 
310     private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
311         @Override
312         public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
313             // If the user has security enabled, show challenge if the setting is changed.
314             if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
315                     && mKeyguardManager.isKeyguardLocked()) {
316                 onLockedNotificationImportanceChange(() -> {
317                     saveImportance.run();
318                     return true;
319                 });
320             } else {
321                 saveImportance.run();
322             }
323         }
324     };
325 
326     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
327         @Override
328         public void onSettingsClick(String key) {
329             try {
330                 mBarService.onNotificationSettingsViewed(key);
331             } catch (RemoteException e) {
332                 // if we're here we're dead
333             }
334         }
335     };
336 
337     private final NotificationInterruptSuppressor mInterruptSuppressor =
338             new NotificationInterruptSuppressor() {
339         @Override
340         public String getName() {
341             return TAG;
342         }
343 
344         @Override
345         public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
346             final StatusBarNotification sbn = entry.getSbn();
347             if (mCentralSurfaces.isOccluded()) {
348                 boolean devicePublic = mLockscreenUserManager
349                         .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
350                 boolean userPublic = devicePublic
351                         || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
352                 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
353                 if (userPublic && needsRedaction) {
354                     // TODO(b/135046837): we can probably relax this with dynamic privacy
355                     return true;
356                 }
357             }
358 
359             if (!mCommandQueue.panelsEnabled()) {
360                 if (DEBUG) {
361                     Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
362                 }
363                 return true;
364             }
365 
366             if (sbn.getNotification().fullScreenIntent != null
367                     && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) {
368                 // we don't allow head-up on the lockscreen (unless there's a
369                 // "showWhenLocked" activity currently showing)  if
370                 // the potential HUN has a fullscreen intent
371                 if (mKeyguardStateController.isShowing() && !mCentralSurfaces.isOccluded()) {
372                     if (DEBUG) {
373                         Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen "
374                                 + sbn.getKey());
375                     }
376                     return true;
377                 }
378 
379                 if (mAccessibilityManager.isTouchExplorationEnabled()) {
380                     if (DEBUG) {
381                         Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
382                     }
383                     return true;
384                 }
385             }
386             return false;
387         }
388 
389         @Override
390         public boolean suppressAwakeInterruptions(NotificationEntry entry) {
391             return isDeviceInVrMode();
392         }
393 
394         @Override
395         public boolean suppressInterruptions(NotificationEntry entry) {
396             return mCentralSurfaces.areNotificationAlertsDisabled();
397         }
398     };
399 }
400