• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 package com.android.systemui.statusbar.notification.row;
17 
18 import static android.app.AppOpsManager.OP_CAMERA;
19 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
20 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
21 
22 import android.app.INotificationManager;
23 import android.app.NotificationChannel;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.LauncherApps;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ShortcutManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.provider.Settings;
36 import android.service.notification.NotificationListenerService;
37 import android.service.notification.StatusBarNotification;
38 import android.util.ArraySet;
39 import android.util.IconDrawableFactory;
40 import android.util.Log;
41 import android.view.HapticFeedbackConstants;
42 import android.view.View;
43 import android.view.accessibility.AccessibilityManager;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.logging.MetricsLogger;
47 import com.android.internal.logging.UiEventLogger;
48 import com.android.internal.logging.nano.MetricsProto;
49 import com.android.internal.statusbar.IStatusBarService;
50 import com.android.settingslib.notification.ConversationIconFactory;
51 import com.android.systemui.CoreStartable;
52 import com.android.systemui.dagger.SysUISingleton;
53 import com.android.systemui.dagger.qualifiers.Background;
54 import com.android.systemui.dagger.qualifiers.Main;
55 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
56 import com.android.systemui.plugins.ActivityStarter;
57 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
58 import com.android.systemui.plugins.statusbar.StatusBarStateController;
59 import com.android.systemui.res.R;
60 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
61 import com.android.systemui.settings.UserContextProvider;
62 import com.android.systemui.shade.ShadeController;
63 import com.android.systemui.shade.ShadeDisplayAware;
64 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
65 import com.android.systemui.statusbar.NotificationPresenter;
66 import com.android.systemui.statusbar.StatusBarState;
67 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
68 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
69 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
70 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
71 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
72 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
73 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
74 import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor;
75 import com.android.systemui.statusbar.notification.row.icon.AppIconProvider;
76 import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
77 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
78 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
79 import com.android.systemui.statusbar.phone.CentralSurfaces;
80 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
81 import com.android.systemui.util.kotlin.JavaAdapter;
82 import com.android.systemui.wmshell.BubblesManager;
83 
84 import java.util.Optional;
85 
86 import javax.inject.Inject;
87 
88 /**
89  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
90  * closing guts, and keeping track of the currently exposed notification guts.
91  */
92 @SysUISingleton
93 public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable {
94     private static final String TAG = "NotificationGutsManager";
95 
96     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
97     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
98 
99     private final MetricsLogger mMetricsLogger;
100     private final Context mContext;
101     private final AccessibilityManager mAccessibilityManager;
102     private final HighPriorityProvider mHighPriorityProvider;
103     private final ChannelEditorDialogController mChannelEditorDialogController;
104     private final PackageDemotionInteractor mPackageDemotionInteractor;
105     private final OnUserInteractionCallback mOnUserInteractionCallback;
106 
107     // Dependencies:
108     private final NotificationLockscreenUserManager mLockscreenUserManager;
109     private final StatusBarStateController mStatusBarStateController;
110     private final IStatusBarService mStatusBarService;
111     private final DeviceProvisionedController mDeviceProvisionedController;
112     private final AssistantFeedbackController mAssistantFeedbackController;
113 
114     // which notification is currently being longpress-examined by the user
115     private NotificationGuts mNotificationGutsExposed;
116     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
117     private NotificationPresenter mPresenter;
118     private NotificationActivityStarter mNotificationActivityStarter;
119     private NotificationListContainer mListContainer;
120     private OnSettingsClickListener mOnSettingsClickListener;
121 
122     private final Handler mMainHandler;
123     private final Handler mBgHandler;
124     private final JavaAdapter mJavaAdapter;
125     private final Optional<BubblesManager> mBubblesManagerOptional;
126     private Runnable mOpenRunnable;
127     private final INotificationManager mNotificationManager;
128     private final AppIconProvider mAppIconProvider;
129     private final NotificationIconStyleProvider mIconStyleProvider;
130     private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
131 
132     private final UserManager mUserManager;
133 
134     private final LauncherApps mLauncherApps;
135     private final ShortcutManager mShortcutManager;
136     private final UserContextProvider mContextTracker;
137     private final UiEventLogger mUiEventLogger;
138     private final ShadeController mShadeController;
139     private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
140     private NotifGutsViewListener mGutsListener;
141     private final HeadsUpManager mHeadsUpManager;
142     private final ActivityStarter mActivityStarter;
143 
144     @Inject
NotificationGutsManager( @hadeDisplayAware Context context, @Main Handler mainHandler, @Background Handler bgHandler, JavaAdapter javaAdapter, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, AppIconProvider appIconProvider, NotificationIconStyleProvider iconStyleProvider, UserManager userManager, PeopleSpaceWidgetManager peopleSpaceWidgetManager, LauncherApps launcherApps, ShortcutManager shortcutManager, ChannelEditorDialogController channelEditorDialogController, PackageDemotionInteractor packageDemotionInteractor, UserContextProvider contextTracker, AssistantFeedbackController assistantFeedbackController, Optional<BubblesManager> bubblesManagerOptional, UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, ShadeController shadeController, WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, NotificationLockscreenUserManager notificationLockscreenUserManager, StatusBarStateController statusBarStateController, IStatusBarService statusBarService, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, HeadsUpManager headsUpManager, ActivityStarter activityStarter)145     public NotificationGutsManager(
146             @ShadeDisplayAware Context context,
147             @Main Handler mainHandler,
148             @Background Handler bgHandler,
149             JavaAdapter javaAdapter,
150             AccessibilityManager accessibilityManager,
151             HighPriorityProvider highPriorityProvider,
152             INotificationManager notificationManager,
153             AppIconProvider appIconProvider,
154             NotificationIconStyleProvider iconStyleProvider,
155             UserManager userManager,
156             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
157             LauncherApps launcherApps,
158             ShortcutManager shortcutManager,
159             ChannelEditorDialogController channelEditorDialogController,
160             PackageDemotionInteractor packageDemotionInteractor,
161             UserContextProvider contextTracker,
162             AssistantFeedbackController assistantFeedbackController,
163             Optional<BubblesManager> bubblesManagerOptional,
164             UiEventLogger uiEventLogger,
165             OnUserInteractionCallback onUserInteractionCallback,
166             ShadeController shadeController,
167             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
168             NotificationLockscreenUserManager notificationLockscreenUserManager,
169             StatusBarStateController statusBarStateController,
170             IStatusBarService statusBarService,
171             DeviceProvisionedController deviceProvisionedController,
172             MetricsLogger metricsLogger,
173             HeadsUpManager headsUpManager,
174             ActivityStarter activityStarter) {
175         mContext = context;
176         mMainHandler = mainHandler;
177         mBgHandler = bgHandler;
178         mJavaAdapter = javaAdapter;
179         mAccessibilityManager = accessibilityManager;
180         mHighPriorityProvider = highPriorityProvider;
181         mNotificationManager = notificationManager;
182         mAppIconProvider = appIconProvider;
183         mIconStyleProvider = iconStyleProvider;
184         mUserManager = userManager;
185         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
186         mLauncherApps = launcherApps;
187         mShortcutManager = shortcutManager;
188         mContextTracker = contextTracker;
189         mChannelEditorDialogController = channelEditorDialogController;
190         mPackageDemotionInteractor = packageDemotionInteractor;
191         mAssistantFeedbackController = assistantFeedbackController;
192         mBubblesManagerOptional = bubblesManagerOptional;
193         mUiEventLogger = uiEventLogger;
194         mOnUserInteractionCallback = onUserInteractionCallback;
195         mShadeController = shadeController;
196         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
197         mLockscreenUserManager = notificationLockscreenUserManager;
198         mStatusBarStateController = statusBarStateController;
199         mStatusBarService = statusBarService;
200         mDeviceProvisionedController = deviceProvisionedController;
201         mMetricsLogger = metricsLogger;
202         mHeadsUpManager = headsUpManager;
203         mActivityStarter = activityStarter;
204     }
205 
setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, OnSettingsClickListener onSettingsClick)206     public void setUpWithPresenter(NotificationPresenter presenter,
207             NotificationListContainer listContainer,
208             OnSettingsClickListener onSettingsClick) {
209         mPresenter = presenter;
210         mListContainer = listContainer;
211         mOnSettingsClickListener = onSettingsClick;
212     }
213 
setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter)214     public void setNotificationActivityStarter(
215             NotificationActivityStarter notificationActivityStarter) {
216         mNotificationActivityStarter = notificationActivityStarter;
217     }
218 
219     @Override
start()220     public void start() {
221         mJavaAdapter.alwaysCollectFlow(
222                 mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(),
223                 this::onLockscreenShadeVisibilityChanged);
224     }
225 
onLockscreenShadeVisibilityChanged(boolean visible)226     private void onLockscreenShadeVisibilityChanged(boolean visible) {
227         if (!visible) {
228             closeAndSaveGuts(
229                     /* removeLeavebehind= */ true ,
230                     /* force= */ true,
231                     /* removeControls= */ true,
232                     /* x= */ -1,
233                     /* y= */ -1,
234                     /* resetMenu= */ true);
235         }
236     }
237 
238     /**
239      * Sends an intent to open the notification settings for a particular package and optional
240      * channel.
241      */
242     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
startAppNotificationSettingsActivity(String packageName, final int appUid, final NotificationChannel channel, ExpandableNotificationRow row)243     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
244             final NotificationChannel channel, ExpandableNotificationRow row) {
245         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
246         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
247         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
248 
249         if (channel != null) {
250             final Bundle args = new Bundle();
251             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
252             args.putString(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
253             intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
254         }
255         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
256     }
257 
startAppDetailsSettingsActivity(String packageName, final int appUid, ExpandableNotificationRow row)258     private void startAppDetailsSettingsActivity(String packageName, final int appUid,
259             ExpandableNotificationRow row) {
260         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
261         intent.setData(Uri.fromParts("package", packageName, null));
262         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
263         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
264         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
265     }
266 
startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops, ExpandableNotificationRow row)267     protected void startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops,
268             ExpandableNotificationRow row) {
269         if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
270             if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
271                 startAppDetailsSettingsActivity(pkg, uid, row);
272             } else {
273                 Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
274                 intent.setData(Uri.fromParts("package", pkg, null));
275                 mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
276             }
277         } else if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
278             Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
279             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg);
280             mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
281         }
282     }
283 
startConversationSettingsActivity(int uid, ExpandableNotificationRow row)284     private void startConversationSettingsActivity(int uid, ExpandableNotificationRow row) {
285         final Intent intent = new Intent(Settings.ACTION_CONVERSATION_SETTINGS);
286         mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
287     }
288 
289     @VisibleForTesting
bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item)290     protected boolean bindGuts(final ExpandableNotificationRow row,
291             NotificationMenuRowPlugin.MenuItem item) {
292 
293         StatusBarNotification sbn = NotificationBundleUi.isEnabled()
294                 ? row.getEntryAdapter().getSbn()
295                 : row.getEntryLegacy().getSbn();
296         NotificationListenerService.Ranking ranking  = NotificationBundleUi.isEnabled()
297                 ? row.getEntryAdapter().getRanking()
298                 : row.getEntryLegacy().getRanking();
299 
300         if (sbn == null || ranking == null) {
301             // only valid for notification rows
302             return false;
303         }
304 
305         row.setGutsView(item);
306         row.setTag(sbn.getPackageName());
307         row.getGuts().setClosedListener((NotificationGuts g) -> {
308             row.onGutsClosed();
309             if (!g.willBeRemoved() && !row.isRemoved()) {
310                 mListContainer.onHeightChanged(
311                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
312             }
313             if (mNotificationGutsExposed == g) {
314                 mNotificationGutsExposed = null;
315                 mGutsMenuItem = null;
316             }
317             if (mGutsListener != null) {
318                 if (NotificationBundleUi.isEnabled()) {
319                     mGutsListener.onGutsClose(row.getEntryAdapter());
320                     row.updateBubbleButton();
321                 } else {
322                     mGutsListener.onGutsClose(row.getEntryLegacy());
323                 }
324             }
325             if(NotificationBundleUi.isEnabled()) {
326                 row.getEntryAdapter().setInlineControlsShown(false);
327             } else {
328                 mHeadsUpManager.setGutsShown(row.getEntryLegacy(), false);
329             }
330         });
331 
332         View gutsView = item.getGutsView();
333 
334         try {
335             if (gutsView instanceof NotificationSnooze) {
336                 initializeSnoozeView(row, sbn, ranking, (NotificationSnooze) gutsView);
337             } else if (gutsView instanceof NotificationInfo) {
338                 initializeNotificationInfo(row, sbn, ranking, (NotificationInfo) gutsView);
339             } else if (gutsView instanceof NotificationConversationInfo) {
340                 initializeConversationNotificationInfo(
341                         row, sbn, ranking, (NotificationConversationInfo) gutsView);
342             } else if (gutsView instanceof PartialConversationInfo) {
343                 initializePartialConversationNotificationInfo(row, sbn, ranking,
344                         (PartialConversationInfo) gutsView);
345             } else if (gutsView instanceof FeedbackInfo) {
346                 initializeFeedbackInfo(row, sbn, ranking, (FeedbackInfo) gutsView);
347             } else if (gutsView instanceof PromotedPermissionGutsContent) {
348                 initializeDemoteView(row, sbn, (PromotedPermissionGutsContent) gutsView);
349             }
350             return true;
351         } catch (Exception e) {
352             Log.e(TAG, "error binding guts", e);
353             return false;
354         }
355     }
356 
357     /**
358      * Sets up the {@link NotificationSnooze} inside the notification row's guts.
359      *
360      * @param row view to set up the guts for
361      * @param notificationSnoozeView view to set up/bind within {@code row}
362      */
initializeSnoozeView( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationSnooze notificationSnoozeView)363     private void initializeSnoozeView(
364             final ExpandableNotificationRow row,
365             final StatusBarNotification sbn,
366             final NotificationListenerService.Ranking ranking,
367             NotificationSnooze notificationSnoozeView) {
368         NotificationGuts guts = row.getGuts();
369 
370         notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
371         notificationSnoozeView.setStatusBarNotification(sbn);
372         notificationSnoozeView.setSnoozeOptions(ranking.getSnoozeCriteria());
373         guts.setHeightChangedListener((NotificationGuts g) -> {
374             mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
375         });
376     }
377 
378     /**
379      * Sets up the {@link NotificationSnooze} inside the notification row's guts.
380      *
381      * @param row view to set up the guts for
382      * @param demoteGuts view to set up/bind within {@code row}
383      */
initializeDemoteView( final ExpandableNotificationRow row, StatusBarNotification sbn, PromotedPermissionGutsContent demoteGuts)384     private void initializeDemoteView(
385             final ExpandableNotificationRow row,
386             StatusBarNotification sbn,
387             PromotedPermissionGutsContent demoteGuts) {
388         demoteGuts.setStatusBarNotification(sbn);
389         demoteGuts.setOnDemoteAction(new View.OnClickListener() {
390             @Override
391             public void onClick(View v) {
392                 try {
393                     // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
394                     mNotificationManager.setCanBePromoted(
395                             sbn.getPackageName(), sbn.getUid(), false, true);
396                 } catch (RemoteException e) {
397                     Log.e(TAG, "Couldn't revoke live update permission", e);
398                 }
399             }
400         });
401     }
402 
403     /**
404      * Sets up the {@link FeedbackInfo} inside the notification row's guts.
405      *
406      * @param row view to set up the guts for
407      * @param feedbackInfo view to set up/bind within {@code row}
408      */
initializeFeedbackInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, FeedbackInfo feedbackInfo)409     private void initializeFeedbackInfo(
410             final ExpandableNotificationRow row,
411             final StatusBarNotification sbn,
412             final NotificationListenerService.Ranking ranking,
413             FeedbackInfo feedbackInfo) {
414         if (mAssistantFeedbackController.getFeedbackIcon(ranking) == null) {
415             return;
416         }
417         UserHandle userHandle = sbn.getUser();
418         PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(mContext,
419                 userHandle.getIdentifier());
420 
421         feedbackInfo.bindGuts(pmUser, sbn, ranking, row, mAssistantFeedbackController,
422                 mStatusBarService, this);
423     }
424 
425     /**
426      * Sets up the {@link NotificationInfo} inside the notification row's guts.
427      * @param row view to set up the guts for
428      * @param notificationInfoView view to set up/bind within {@code row}
429      */
430     @VisibleForTesting
initializeNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationInfo notificationInfoView)431     void initializeNotificationInfo(
432             final ExpandableNotificationRow row,
433             final StatusBarNotification sbn,
434             final NotificationListenerService.Ranking ranking,
435             NotificationInfo notificationInfoView) throws Exception {
436         NotificationGuts guts = row.getGuts();
437         String packageName = sbn.getPackageName();
438         // Settings link is only valid for notifications that specify a non-system user
439         NotificationInfo.OnSettingsClickListener onSettingsClick = null;
440         UserHandle userHandle = sbn.getUser();
441         PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(
442                 mContext, userHandle.getIdentifier());
443         final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick =
444                 (View v, Intent intent) -> {
445                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
446                     guts.resetFalsingCheck();
447                     mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
448                             row);
449                 };
450 
451         if (!userHandle.equals(UserHandle.ALL)
452                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
453             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
454                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
455                 guts.resetFalsingCheck();
456                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
457                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
458             };
459         }
460 
461         NotificationInfo.OnFeedbackClickListener onNasFeedbackClick = (View v, Intent intent) -> {
462             guts.resetFalsingCheck();
463             mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), row);
464         };
465 
466         notificationInfoView.bindNotification(
467                 pmUser,
468                 mNotificationManager,
469                 mAppIconProvider,
470                 mIconStyleProvider,
471                 mOnUserInteractionCallback,
472                 mChannelEditorDialogController,
473                 mPackageDemotionInteractor,
474                 packageName,
475                 ranking,
476                 sbn,
477                 NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(),
478                 NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null,
479                 onSettingsClick,
480                 onAppSettingsClick,
481                 onNasFeedbackClick,
482                 mUiEventLogger,
483                 mDeviceProvisionedController.isDeviceProvisioned(),
484                 NotificationBundleUi.isEnabled()
485                         ? !row.getEntryAdapter().isBlockable()
486                         : row.getIsNonblockable(),
487                 row.canViewBeDismissed(),
488                 NotificationBundleUi.isEnabled()
489                         ? row.getEntryAdapter().isHighPriority()
490                         : mHighPriorityProvider.isHighPriority(row.getEntryLegacy()),
491                 mAssistantFeedbackController,
492                 mMetricsLogger,
493                 row.getCloseButtonOnClickListener(row));
494     }
495 
496     /**
497      * Sets up the {@link PartialConversationInfo} inside the notification row's guts.
498      * @param row view to set up the guts for
499      * @param notificationInfoView view to set up/bind within {@code row}
500      */
501     @VisibleForTesting
initializePartialConversationNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, PartialConversationInfo notificationInfoView)502     void initializePartialConversationNotificationInfo(
503             final ExpandableNotificationRow row,
504             final StatusBarNotification sbn,
505             final NotificationListenerService.Ranking ranking,
506             PartialConversationInfo notificationInfoView) throws Exception {
507         NotificationGuts guts = row.getGuts();
508         String packageName = sbn.getPackageName();
509         // Settings link is only valid for notifications that specify a non-system user
510         NotificationInfo.OnSettingsClickListener onSettingsClick = null;
511         UserHandle userHandle = sbn.getUser();
512         PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(
513                 mContext, userHandle.getIdentifier());
514 
515         if (!userHandle.equals(UserHandle.ALL)
516                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
517             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
518                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
519                 guts.resetFalsingCheck();
520                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
521                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
522             };
523         }
524 
525         notificationInfoView.bindNotification(
526                 pmUser,
527                 mNotificationManager,
528                 mChannelEditorDialogController,
529                 packageName,
530                 ranking,
531                 sbn,
532                 onSettingsClick,
533                 mDeviceProvisionedController.isDeviceProvisioned(),
534                 NotificationBundleUi.isEnabled()
535                         ? !row.getEntryAdapter().isBlockable()
536                         : row.getIsNonblockable());
537     }
538 
539     /**
540      * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
541      * @param row view to set up the guts for
542      * @param notificationInfoView view to set up/bind within {@code row}
543      */
544     @VisibleForTesting
initializeConversationNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationConversationInfo notificationInfoView)545     void initializeConversationNotificationInfo(
546             final ExpandableNotificationRow row,
547             final StatusBarNotification sbn,
548             final NotificationListenerService.Ranking ranking,
549             NotificationConversationInfo notificationInfoView) throws Exception {
550         NotificationGuts guts = row.getGuts();
551         String packageName = sbn.getPackageName();
552         // Settings link is only valid for notifications that specify a non-system user
553         NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
554         UserHandle userHandle = sbn.getUser();
555         PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(
556                 mContext, userHandle.getIdentifier());
557         final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
558                 (View v, Intent intent) -> {
559                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
560                     guts.resetFalsingCheck();
561                     mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
562                             row);
563                 };
564 
565         final NotificationConversationInfo.OnConversationSettingsClickListener
566                 onConversationSettingsListener =
567                 () -> {
568                     startConversationSettingsActivity(sbn.getUid(), row);
569                 };
570 
571         if (!userHandle.equals(UserHandle.ALL)
572                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
573             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
574                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
575                 guts.resetFalsingCheck();
576                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
577                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
578             };
579         }
580         ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
581                 mLauncherApps, pmUser, IconDrawableFactory.newInstance(mContext, false),
582                 mContext.getResources().getDimensionPixelSize(
583                         R.dimen.notification_guts_conversation_icon_size));
584 
585         NotificationInfo.OnFeedbackClickListener onNasFeedbackClick = (View v, Intent intent) -> {
586             guts.resetFalsingCheck();
587             mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), row);
588         };
589 
590         notificationInfoView.bindNotification(
591                 mShortcutManager,
592                 pmUser,
593                 mUserManager,
594                 mPeopleSpaceWidgetManager,
595                 mNotificationManager,
596                 mOnUserInteractionCallback,
597                 packageName,
598                 NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(),
599                 NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null,
600                 ranking,
601                 sbn,
602                 onSettingsClick,
603                 onNasFeedbackClick,
604                 iconFactoryLoader,
605                 mContextTracker.getUserContext(),
606                 mDeviceProvisionedController.isDeviceProvisioned(),
607                 mMainHandler,
608                 mBgHandler,
609                 onConversationSettingsListener,
610                 mBubblesManagerOptional,
611                 mShadeController,
612                 row.canViewBeDismissed(),
613                 row.getCloseButtonOnClickListener(row));
614     }
615 
616     /**
617      * Closes guts or notification menus that might be visible and saves any changes if applicable
618      * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}).
619      *
620      * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
621      * @param force true if guts should be closed regardless of state (used for snooze only).
622      * @param removeControls true if controls (e.g. info) should be closed.
623      * @param x if closed based on touch location, this is the x touch location.
624      * @param y if closed based on touch location, this is the y touch location.
625      * @param resetMenu if any notification menus that might be revealed should be closed.
626      */
closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, int x, int y, boolean resetMenu)627     public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
628             int x, int y, boolean resetMenu) {
629         if (mNotificationGutsExposed != null) {
630             mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
631             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
632         }
633         if (resetMenu && mListContainer != null) {
634             mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
635         }
636     }
637 
638     /**
639      * Closes all guts that might be visible without saving changes.
640      */
closeAndUndoGuts()641     public void closeAndUndoGuts() {
642         if (mNotificationGutsExposed != null) {
643             mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
644             mNotificationGutsExposed.closeControls(
645                     /* x = */ -1,
646                     /* y = */ -1,
647                     /* save = */ false,
648                     /* force = */ false);
649         }
650     }
651 
652     /**
653      * Returns the exposed NotificationGuts or null if none are exposed.
654      */
getExposedGuts()655     public NotificationGuts getExposedGuts() {
656         return mNotificationGutsExposed;
657     }
658 
659     @VisibleForTesting
setExposedGuts(NotificationGuts guts)660     public void setExposedGuts(NotificationGuts guts) {
661         mNotificationGutsExposed = guts;
662     }
663 
getNotificationLongClicker()664     public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
665         return this::openGuts;
666     }
667 
668     /**
669      * Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for
670      * the normal half-swipe and long-press use cases via a circular reveal. When the blocking
671      * helper needs to be shown on the row, this will skip the circular reveal.
672      *
673      * @param view ExpandableNotificationRow to open guts on
674      * @param x x coordinate of origin of circular reveal
675      * @param y y coordinate of origin of circular reveal
676      * @param menuItem MenuItem the guts should display
677      * @return true if guts was opened
678      */
openGuts( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)679     public boolean openGuts(
680             View view,
681             int x,
682             int y,
683             NotificationMenuRowPlugin.MenuItem menuItem) {
684         if (menuItem.getGutsView() instanceof NotificationGuts.GutsContent) {
685             NotificationGuts.GutsContent gutsView =
686                     (NotificationGuts.GutsContent)  menuItem.getGutsView();
687             if (gutsView.needsFalsingProtection()) {
688                 if (mStatusBarStateController instanceof StatusBarStateControllerImpl) {
689                     ((StatusBarStateControllerImpl) mStatusBarStateController)
690                             .setLeaveOpenOnKeyguardHide(true);
691                 }
692 
693                 Runnable r = () -> mMainHandler.post(
694                         () -> openGutsInternal(view, x, y, menuItem));
695                 // If the bouncer shows, it will block the TOUCH_UP event from reaching the notif,
696                 // so explicitly mark it as unpressed here to reset the touch animation.
697                 view.setPressed(false);
698                 mActivityStarter.executeRunnableDismissingKeyguard(
699                         r,
700                         null /* cancelAction */,
701                         false /* dismissShade */,
702                         true /* afterKeyguardGone */,
703                         true /* deferred */);
704                 return true;
705                 /**
706                  * When {@link CentralSurfaces} doesn't exist, falling through to call
707                  * {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}.
708                  */
709             }
710         }
711         return openGutsInternal(view, x, y, menuItem);
712     }
713 
714     @VisibleForTesting
openGutsInternal( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)715     boolean openGutsInternal(
716             View view,
717             int x,
718             int y,
719             NotificationMenuRowPlugin.MenuItem menuItem) {
720 
721         if (!(view instanceof ExpandableNotificationRow)) {
722             return false;
723         }
724 
725         if (view.getWindowToken() == null) {
726             Log.e(TAG, "Trying to show notification guts, but not attached to window");
727             return false;
728         }
729 
730         final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
731         if (row.isNotificationRowLongClickable()) {
732             view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
733         }
734         if (row.areGutsExposed()) {
735             closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
736                     true /* removeControls */, -1 /* x */, -1 /* y */,
737                     true /* resetMenu */);
738             return false;
739         }
740 
741         row.ensureGutsInflated();
742         NotificationGuts guts = row.getGuts();
743         mNotificationGutsExposed = guts;
744         if (!bindGuts(row, menuItem)) {
745             // exception occurred trying to fill in all the data, bail.
746             return false;
747         }
748 
749 
750         // Assume we are a status_bar_notification_row
751         if (guts == null) {
752             // This view has no guts. Examples are the more card or the dismiss all view
753             return false;
754         }
755 
756         // ensure that it's laid but not visible until actually laid out
757         guts.setVisibility(View.INVISIBLE);
758         // Post to ensure the the guts are properly laid out.
759         mOpenRunnable = new Runnable() {
760             @Override
761             public void run() {
762                 if (row.getWindowToken() == null) {
763                     Log.e(TAG, "Trying to show notification guts in post(), but not attached to "
764                             + "window");
765                     return;
766                 }
767                 guts.setVisibility(View.VISIBLE);
768 
769                 final boolean needsFalsingProtection =
770                         (mStatusBarStateController.getState() == StatusBarState.KEYGUARD &&
771                                 !mAccessibilityManager.isTouchExplorationEnabled());
772 
773                 guts.openControls(
774                         x,
775                         y,
776                         needsFalsingProtection,
777                         row::onGutsOpened);
778 
779                 if (mGutsListener != null) {
780                     if(NotificationBundleUi.isEnabled()) {
781                         mGutsListener.onGutsOpen(row.getEntryAdapter(), guts);
782                     } else {
783                         mGutsListener.onGutsOpen(row.getEntryLegacy(), guts);
784                     }
785                 }
786 
787                 row.closeRemoteInput();
788                 mListContainer.onHeightChanged(row, true /* needsAnimation */);
789                 mGutsMenuItem = menuItem;
790                 if(NotificationBundleUi.isEnabled()) {
791                     row.getEntryAdapter().setInlineControlsShown(true);
792                 } else {
793                     mHeadsUpManager.setGutsShown(row.getEntryLegacy(), true);
794                 }
795             }
796         };
797         guts.post(mOpenRunnable);
798         return true;
799     }
800 
801     /**
802      * @param gutsListener the listener for open and close guts events
803      */
setGutsListener(NotifGutsViewListener gutsListener)804     public void setGutsListener(NotifGutsViewListener gutsListener) {
805         mGutsListener = gutsListener;
806     }
807 
808     public interface OnSettingsClickListener {
onSettingsClick(String key)809         public void onSettingsClick(String key);
810     }
811 }
812