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.appwidget.AppWidgetManager;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.LauncherApps;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ShortcutManager;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.UserHandle;
34 import android.provider.Settings;
35 import android.service.notification.StatusBarNotification;
36 import android.util.ArraySet;
37 import android.util.IconDrawableFactory;
38 import android.util.Log;
39 import android.view.HapticFeedbackConstants;
40 import android.view.View;
41 import android.view.accessibility.AccessibilityManager;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.logging.MetricsLogger;
45 import com.android.internal.logging.UiEventLogger;
46 import com.android.internal.logging.nano.MetricsProto;
47 import com.android.settingslib.notification.ConversationIconFactory;
48 import com.android.systemui.Dependency;
49 import com.android.systemui.Dumpable;
50 import com.android.systemui.R;
51 import com.android.systemui.dagger.qualifiers.Background;
52 import com.android.systemui.dagger.qualifiers.Main;
53 import com.android.systemui.dump.DumpManager;
54 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
55 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
56 import com.android.systemui.plugins.statusbar.StatusBarStateController;
57 import com.android.systemui.settings.UserContextProvider;
58 import com.android.systemui.statusbar.NotificationLifetimeExtender;
59 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
60 import com.android.systemui.statusbar.NotificationPresenter;
61 import com.android.systemui.statusbar.StatusBarState;
62 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
63 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
64 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
65 import com.android.systemui.statusbar.notification.NotificationEntryManager;
66 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
67 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
68 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
69 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
70 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
71 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
72 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
73 import com.android.systemui.statusbar.phone.ShadeController;
74 import com.android.systemui.statusbar.phone.StatusBar;
75 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
76 import com.android.systemui.wmshell.BubblesManager;
77 
78 import java.io.FileDescriptor;
79 import java.io.PrintWriter;
80 import java.util.Optional;
81 
82 import dagger.Lazy;
83 
84 /**
85  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
86  * closing guts, and keeping track of the currently exposed notification guts.
87  */
88 public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
89         NotifGutsViewManager {
90     private static final String TAG = "NotificationGutsManager";
91 
92     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
93     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
94 
95     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
96     private final Context mContext;
97     private final AccessibilityManager mAccessibilityManager;
98     private final HighPriorityProvider mHighPriorityProvider;
99     private final ChannelEditorDialogController mChannelEditorDialogController;
100     private final OnUserInteractionCallback mOnUserInteractionCallback;
101 
102     // Dependencies:
103     private final NotificationLockscreenUserManager mLockscreenUserManager =
104             Dependency.get(NotificationLockscreenUserManager.class);
105     private final StatusBarStateController mStatusBarStateController =
106             Dependency.get(StatusBarStateController.class);
107     private final DeviceProvisionedController mDeviceProvisionedController =
108             Dependency.get(DeviceProvisionedController.class);
109     private final AssistantFeedbackController mAssistantFeedbackController;
110 
111     // which notification is currently being longpress-examined by the user
112     private NotificationGuts mNotificationGutsExposed;
113     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
114     private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
115     private NotificationPresenter mPresenter;
116     private NotificationActivityStarter mNotificationActivityStarter;
117     private NotificationListContainer mListContainer;
118     private CheckSaveListener mCheckSaveListener;
119     private OnSettingsClickListener mOnSettingsClickListener;
120     @VisibleForTesting
121     protected String mKeyToRemoveOnGutsClosed;
122 
123     private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
124     private final Handler mMainHandler;
125     private final Handler mBgHandler;
126     private final Optional<BubblesManager> mBubblesManagerOptional;
127     private Runnable mOpenRunnable;
128     private final INotificationManager mNotificationManager;
129     private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
130     private final LauncherApps mLauncherApps;
131     private final ShortcutManager mShortcutManager;
132     private final UserContextProvider mContextTracker;
133     private final UiEventLogger mUiEventLogger;
134     private final ShadeController mShadeController;
135     private final AppWidgetManager mAppWidgetManager;
136     private NotifGutsViewListener mGutsListener;
137 
138     /**
139      * Injected constructor. See {@link NotificationsModule}.
140      */
NotificationGutsManager(Context context, Lazy<Optional<StatusBar>> statusBarOptionalLazy, @Main Handler mainHandler, @Background Handler bgHandler, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, NotificationEntryManager notificationEntryManager, PeopleSpaceWidgetManager peopleSpaceWidgetManager, LauncherApps launcherApps, ShortcutManager shortcutManager, ChannelEditorDialogController channelEditorDialogController, UserContextProvider contextTracker, AssistantFeedbackController assistantFeedbackController, Optional<BubblesManager> bubblesManagerOptional, UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, ShadeController shadeController, DumpManager dumpManager)141     public NotificationGutsManager(Context context,
142             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
143             @Main Handler mainHandler,
144             @Background Handler bgHandler,
145             AccessibilityManager accessibilityManager,
146             HighPriorityProvider highPriorityProvider,
147             INotificationManager notificationManager,
148             NotificationEntryManager notificationEntryManager,
149             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
150             LauncherApps launcherApps,
151             ShortcutManager shortcutManager,
152             ChannelEditorDialogController channelEditorDialogController,
153             UserContextProvider contextTracker,
154             AssistantFeedbackController assistantFeedbackController,
155             Optional<BubblesManager> bubblesManagerOptional,
156             UiEventLogger uiEventLogger,
157             OnUserInteractionCallback onUserInteractionCallback,
158             ShadeController shadeController,
159             DumpManager dumpManager) {
160         mContext = context;
161         mStatusBarOptionalLazy = statusBarOptionalLazy;
162         mMainHandler = mainHandler;
163         mBgHandler = bgHandler;
164         mAccessibilityManager = accessibilityManager;
165         mHighPriorityProvider = highPriorityProvider;
166         mNotificationManager = notificationManager;
167         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
168         mLauncherApps = launcherApps;
169         mShortcutManager = shortcutManager;
170         mContextTracker = contextTracker;
171         mChannelEditorDialogController = channelEditorDialogController;
172         mAssistantFeedbackController = assistantFeedbackController;
173         mBubblesManagerOptional = bubblesManagerOptional;
174         mUiEventLogger = uiEventLogger;
175         mOnUserInteractionCallback = onUserInteractionCallback;
176         mShadeController = shadeController;
177         mAppWidgetManager = AppWidgetManager.getInstance(context);
178 
179         dumpManager.registerDumpable(this);
180     }
181 
setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick)182     public void setUpWithPresenter(NotificationPresenter presenter,
183             NotificationListContainer listContainer,
184             CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
185         mPresenter = presenter;
186         mListContainer = listContainer;
187         mCheckSaveListener = checkSave;
188         mOnSettingsClickListener = onSettingsClick;
189     }
190 
setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter)191     public void setNotificationActivityStarter(
192             NotificationActivityStarter notificationActivityStarter) {
193         mNotificationActivityStarter = notificationActivityStarter;
194     }
195 
onDensityOrFontScaleChanged(NotificationEntry entry)196     public void onDensityOrFontScaleChanged(NotificationEntry entry) {
197         setExposedGuts(entry.getGuts());
198         bindGuts(entry.getRow());
199     }
200 
201     /**
202      * Sends an intent to open the notification settings for a particular package and optional
203      * channel.
204      */
205     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
startAppNotificationSettingsActivity(String packageName, final int appUid, final NotificationChannel channel, ExpandableNotificationRow row)206     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
207             final NotificationChannel channel, ExpandableNotificationRow row) {
208         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
209         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
210         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
211 
212         if (channel != null) {
213             final Bundle args = new Bundle();
214             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
215             args.putString(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
216             intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
217         }
218         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
219     }
220 
startAppDetailsSettingsActivity(String packageName, final int appUid, final NotificationChannel channel, ExpandableNotificationRow row)221     private void startAppDetailsSettingsActivity(String packageName, final int appUid,
222             final NotificationChannel channel, ExpandableNotificationRow row) {
223         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
224         intent.setData(Uri.fromParts("package", packageName, null));
225         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
226         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
227         if (channel != null) {
228             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
229         }
230         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
231     }
232 
startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops, ExpandableNotificationRow row)233     protected void startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops,
234             ExpandableNotificationRow row) {
235         if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
236             if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
237                 startAppDetailsSettingsActivity(pkg, uid, null, row);
238             } else {
239                 Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
240                 intent.setData(Uri.fromParts("package", pkg, null));
241                 mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
242             }
243         } else if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
244             Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
245             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg);
246             mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
247         }
248     }
249 
startConversationSettingsActivity(int uid, ExpandableNotificationRow row)250     private void startConversationSettingsActivity(int uid, ExpandableNotificationRow row) {
251         final Intent intent = new Intent(Settings.ACTION_CONVERSATION_SETTINGS);
252         mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
253     }
254 
bindGuts(final ExpandableNotificationRow row)255     private boolean bindGuts(final ExpandableNotificationRow row) {
256         row.ensureGutsInflated();
257         return bindGuts(row, mGutsMenuItem);
258     }
259 
260     @VisibleForTesting
bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item)261     protected boolean bindGuts(final ExpandableNotificationRow row,
262             NotificationMenuRowPlugin.MenuItem item) {
263         NotificationEntry entry = row.getEntry();
264 
265         row.setGutsView(item);
266         row.setTag(entry.getSbn().getPackageName());
267         row.getGuts().setClosedListener((NotificationGuts g) -> {
268             row.onGutsClosed();
269             if (!g.willBeRemoved() && !row.isRemoved()) {
270                 mListContainer.onHeightChanged(
271                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
272             }
273             if (mNotificationGutsExposed == g) {
274                 mNotificationGutsExposed = null;
275                 mGutsMenuItem = null;
276             }
277             if (mGutsListener != null) {
278                 mGutsListener.onGutsClose(entry);
279             }
280             String key = entry.getKey();
281             if (key.equals(mKeyToRemoveOnGutsClosed)) {
282                 mKeyToRemoveOnGutsClosed = null;
283                 if (mNotificationLifetimeFinishedCallback != null) {
284                     mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
285                 }
286             }
287         });
288 
289         View gutsView = item.getGutsView();
290         try {
291             if (gutsView instanceof NotificationSnooze) {
292                 initializeSnoozeView(row, (NotificationSnooze) gutsView);
293             } else if (gutsView instanceof NotificationInfo) {
294                 initializeNotificationInfo(row, (NotificationInfo) gutsView);
295             } else if (gutsView instanceof NotificationConversationInfo) {
296                 initializeConversationNotificationInfo(
297                         row, (NotificationConversationInfo) gutsView);
298             } else if (gutsView instanceof PartialConversationInfo) {
299                 initializePartialConversationNotificationInfo(row,
300                         (PartialConversationInfo) gutsView);
301             } else if (gutsView instanceof FeedbackInfo) {
302                 initializeFeedbackInfo(row, (FeedbackInfo) gutsView);
303             }
304             return true;
305         } catch (Exception e) {
306             Log.e(TAG, "error binding guts", e);
307             return false;
308         }
309     }
310 
311     /**
312      * Sets up the {@link NotificationSnooze} inside the notification row's guts.
313      *
314      * @param row view to set up the guts for
315      * @param notificationSnoozeView view to set up/bind within {@code row}
316      */
initializeSnoozeView( final ExpandableNotificationRow row, NotificationSnooze notificationSnoozeView)317     private void initializeSnoozeView(
318             final ExpandableNotificationRow row,
319             NotificationSnooze notificationSnoozeView) {
320         NotificationGuts guts = row.getGuts();
321         StatusBarNotification sbn = row.getEntry().getSbn();
322 
323         notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
324         notificationSnoozeView.setStatusBarNotification(sbn);
325         notificationSnoozeView.setSnoozeOptions(row.getEntry().getSnoozeCriteria());
326         guts.setHeightChangedListener((NotificationGuts g) -> {
327             mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
328         });
329     }
330 
331     /**
332      * Sets up the {@link FeedbackInfo} inside the notification row's guts.
333      *
334      * @param row view to set up the guts for
335      * @param feedbackInfo view to set up/bind within {@code row}
336      */
initializeFeedbackInfo( final ExpandableNotificationRow row, FeedbackInfo feedbackInfo)337     private void initializeFeedbackInfo(
338             final ExpandableNotificationRow row,
339             FeedbackInfo feedbackInfo) {
340         StatusBarNotification sbn = row.getEntry().getSbn();
341         UserHandle userHandle = sbn.getUser();
342         PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
343                 userHandle.getIdentifier());
344 
345         if (mAssistantFeedbackController.showFeedbackIndicator(row.getEntry())) {
346             feedbackInfo.bindGuts(pmUser, sbn, row.getEntry(), row, mAssistantFeedbackController);
347         }
348     }
349 
350     /**
351      * Sets up the {@link NotificationInfo} inside the notification row's guts.
352      * @param row view to set up the guts for
353      * @param notificationInfoView view to set up/bind within {@code row}
354      */
355     @VisibleForTesting
initializeNotificationInfo( final ExpandableNotificationRow row, NotificationInfo notificationInfoView)356     void initializeNotificationInfo(
357             final ExpandableNotificationRow row,
358             NotificationInfo notificationInfoView) throws Exception {
359         NotificationGuts guts = row.getGuts();
360         StatusBarNotification sbn = row.getEntry().getSbn();
361         String packageName = sbn.getPackageName();
362         // Settings link is only valid for notifications that specify a non-system user
363         NotificationInfo.OnSettingsClickListener onSettingsClick = null;
364         UserHandle userHandle = sbn.getUser();
365         PackageManager pmUser = StatusBar.getPackageManagerForUser(
366                 mContext, userHandle.getIdentifier());
367         final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick =
368                 (View v, Intent intent) -> {
369                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
370                     guts.resetFalsingCheck();
371                     mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
372                             row);
373                 };
374 
375         if (!userHandle.equals(UserHandle.ALL)
376                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
377             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
378                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
379                 guts.resetFalsingCheck();
380                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
381                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
382             };
383         }
384 
385         notificationInfoView.bindNotification(
386                 pmUser,
387                 mNotificationManager,
388                 mOnUserInteractionCallback,
389                 mChannelEditorDialogController,
390                 packageName,
391                 row.getEntry().getChannel(),
392                 row.getUniqueChannels(),
393                 row.getEntry(),
394                 onSettingsClick,
395                 onAppSettingsClick,
396                 mUiEventLogger,
397                 mDeviceProvisionedController.isDeviceProvisioned(),
398                 row.getIsNonblockable(),
399                 mHighPriorityProvider.isHighPriority(row.getEntry()),
400                 mAssistantFeedbackController);
401     }
402 
403     /**
404      * Sets up the {@link PartialConversationInfo} inside the notification row's guts.
405      * @param row view to set up the guts for
406      * @param notificationInfoView view to set up/bind within {@code row}
407      */
408     @VisibleForTesting
initializePartialConversationNotificationInfo( final ExpandableNotificationRow row, PartialConversationInfo notificationInfoView)409     void initializePartialConversationNotificationInfo(
410             final ExpandableNotificationRow row,
411             PartialConversationInfo notificationInfoView) throws Exception {
412         NotificationGuts guts = row.getGuts();
413         StatusBarNotification sbn = row.getEntry().getSbn();
414         String packageName = sbn.getPackageName();
415         // Settings link is only valid for notifications that specify a non-system user
416         NotificationInfo.OnSettingsClickListener onSettingsClick = null;
417         UserHandle userHandle = sbn.getUser();
418         PackageManager pmUser = StatusBar.getPackageManagerForUser(
419                 mContext, userHandle.getIdentifier());
420 
421         if (!userHandle.equals(UserHandle.ALL)
422                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
423             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
424                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
425                 guts.resetFalsingCheck();
426                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
427                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
428             };
429         }
430 
431         notificationInfoView.bindNotification(
432                 pmUser,
433                 mNotificationManager,
434                 mChannelEditorDialogController,
435                 packageName,
436                 row.getEntry().getChannel(),
437                 row.getUniqueChannels(),
438                 row.getEntry(),
439                 onSettingsClick,
440                 mDeviceProvisionedController.isDeviceProvisioned(),
441                 row.getIsNonblockable());
442     }
443 
444     /**
445      * Sets up the {@link ConversationInfo} inside the notification row's guts.
446      * @param row view to set up the guts for
447      * @param notificationInfoView view to set up/bind within {@code row}
448      */
449     @VisibleForTesting
initializeConversationNotificationInfo( final ExpandableNotificationRow row, NotificationConversationInfo notificationInfoView)450     void initializeConversationNotificationInfo(
451             final ExpandableNotificationRow row,
452             NotificationConversationInfo notificationInfoView) throws Exception {
453         NotificationGuts guts = row.getGuts();
454         NotificationEntry entry = row.getEntry();
455         StatusBarNotification sbn = entry.getSbn();
456         String packageName = sbn.getPackageName();
457         // Settings link is only valid for notifications that specify a non-system user
458         NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
459         UserHandle userHandle = sbn.getUser();
460         PackageManager pmUser = StatusBar.getPackageManagerForUser(
461                 mContext, userHandle.getIdentifier());
462         final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
463                 (View v, Intent intent) -> {
464                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
465                     guts.resetFalsingCheck();
466                     mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
467                             row);
468                 };
469 
470         final NotificationConversationInfo.OnConversationSettingsClickListener
471                 onConversationSettingsListener =
472                 () -> {
473                     startConversationSettingsActivity(sbn.getUid(), row);
474                 };
475 
476         if (!userHandle.equals(UserHandle.ALL)
477                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
478             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
479                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
480                 guts.resetFalsingCheck();
481                 mOnSettingsClickListener.onSettingsClick(sbn.getKey());
482                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
483             };
484         }
485         ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
486                 mLauncherApps, pmUser, IconDrawableFactory.newInstance(mContext, false),
487                 mContext.getResources().getDimensionPixelSize(
488                         R.dimen.notification_guts_conversation_icon_size));
489 
490         notificationInfoView.bindNotification(
491                 notificationInfoView.getSelectedAction(),
492                 mShortcutManager,
493                 pmUser,
494                 mPeopleSpaceWidgetManager,
495                 mNotificationManager,
496                 mOnUserInteractionCallback,
497                 packageName,
498                 entry.getChannel(),
499                 entry,
500                 entry.getBubbleMetadata(),
501                 onSettingsClick,
502                 iconFactoryLoader,
503                 mContextTracker.getUserContext(),
504                 mDeviceProvisionedController.isDeviceProvisioned(),
505                 mMainHandler,
506                 mBgHandler,
507                 onConversationSettingsListener,
508                 mBubblesManagerOptional,
509                 mShadeController);
510     }
511 
512     /**
513      * Closes guts or notification menus that might be visible and saves any changes.
514      *
515      * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
516      * @param force true if guts should be closed regardless of state (used for snooze only).
517      * @param removeControls true if controls (e.g. info) should be closed.
518      * @param x if closed based on touch location, this is the x touch location.
519      * @param y if closed based on touch location, this is the y touch location.
520      * @param resetMenu if any notification menus that might be revealed should be closed.
521      */
closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, int x, int y, boolean resetMenu)522     public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
523             int x, int y, boolean resetMenu) {
524         if (mNotificationGutsExposed != null) {
525             mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
526             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
527         }
528         if (resetMenu) {
529             mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
530         }
531     }
532 
533     /**
534      * Returns the exposed NotificationGuts or null if none are exposed.
535      */
getExposedGuts()536     public NotificationGuts getExposedGuts() {
537         return mNotificationGutsExposed;
538     }
539 
setExposedGuts(NotificationGuts guts)540     public void setExposedGuts(NotificationGuts guts) {
541         mNotificationGutsExposed = guts;
542     }
543 
getNotificationLongClicker()544     public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
545         return this::openGuts;
546     }
547 
548     /**
549      * Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for
550      * the normal half-swipe and long-press use cases via a circular reveal. When the blocking
551      * helper needs to be shown on the row, this will skip the circular reveal.
552      *
553      * @param view ExpandableNotificationRow to open guts on
554      * @param x x coordinate of origin of circular reveal
555      * @param y y coordinate of origin of circular reveal
556      * @param menuItem MenuItem the guts should display
557      * @return true if guts was opened
558      */
openGuts( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)559     public boolean openGuts(
560             View view,
561             int x,
562             int y,
563             NotificationMenuRowPlugin.MenuItem menuItem) {
564         if (menuItem.getGutsView() instanceof NotificationGuts.GutsContent) {
565             NotificationGuts.GutsContent gutsView =
566                     (NotificationGuts.GutsContent)  menuItem.getGutsView();
567             if (gutsView.needsFalsingProtection()) {
568                 if (mStatusBarStateController instanceof StatusBarStateControllerImpl) {
569                     ((StatusBarStateControllerImpl) mStatusBarStateController)
570                             .setLeaveOpenOnKeyguardHide(true);
571                 }
572 
573                 Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
574                 if (statusBarOptional.isPresent()) {
575                     Runnable r = () -> mMainHandler.post(
576                             () -> openGutsInternal(view, x, y, menuItem));
577                     statusBarOptional.get().executeRunnableDismissingKeyguard(
578                             r,
579                             null /* cancelAction */,
580                             false /* dismissShade */,
581                             true /* afterKeyguardGone */,
582                             true /* deferred */);
583                     return true;
584                 }
585                 /**
586                  * When {@link StatusBar} doesn't exist, falling through to call
587                  * {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}.
588                  */
589             }
590         }
591         return openGutsInternal(view, x, y, menuItem);
592     }
593 
594     @VisibleForTesting
openGutsInternal( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)595     boolean openGutsInternal(
596             View view,
597             int x,
598             int y,
599             NotificationMenuRowPlugin.MenuItem menuItem) {
600 
601         if (!(view instanceof ExpandableNotificationRow)) {
602             return false;
603         }
604 
605         if (view.getWindowToken() == null) {
606             Log.e(TAG, "Trying to show notification guts, but not attached to window");
607             return false;
608         }
609 
610         final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
611         view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
612         if (row.areGutsExposed()) {
613             closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
614                     true /* removeControls */, -1 /* x */, -1 /* y */,
615                     true /* resetMenu */);
616             return false;
617         }
618 
619         row.ensureGutsInflated();
620         NotificationGuts guts = row.getGuts();
621         mNotificationGutsExposed = guts;
622         if (!bindGuts(row, menuItem)) {
623             // exception occurred trying to fill in all the data, bail.
624             return false;
625         }
626 
627 
628         // Assume we are a status_bar_notification_row
629         if (guts == null) {
630             // This view has no guts. Examples are the more card or the dismiss all view
631             return false;
632         }
633 
634         // ensure that it's laid but not visible until actually laid out
635         guts.setVisibility(View.INVISIBLE);
636         // Post to ensure the the guts are properly laid out.
637         mOpenRunnable = new Runnable() {
638             @Override
639             public void run() {
640                 if (row.getWindowToken() == null) {
641                     Log.e(TAG, "Trying to show notification guts in post(), but not attached to "
642                             + "window");
643                     return;
644                 }
645                 guts.setVisibility(View.VISIBLE);
646 
647                 final boolean needsFalsingProtection =
648                         (mStatusBarStateController.getState() == StatusBarState.KEYGUARD &&
649                                 !mAccessibilityManager.isTouchExplorationEnabled());
650 
651                 guts.openControls(
652                         !row.isBlockingHelperShowing(),
653                         x,
654                         y,
655                         needsFalsingProtection,
656                         row::onGutsOpened);
657 
658                 if (mGutsListener != null) {
659                     mGutsListener.onGutsOpen(row.getEntry(), guts);
660                 }
661 
662                 row.closeRemoteInput();
663                 mListContainer.onHeightChanged(row, true /* needsAnimation */);
664                 mGutsMenuItem = menuItem;
665             }
666         };
667         guts.post(mOpenRunnable);
668         return true;
669     }
670 
671     @Override
setCallback(NotificationSafeToRemoveCallback callback)672     public void setCallback(NotificationSafeToRemoveCallback callback) {
673         mNotificationLifetimeFinishedCallback = callback;
674     }
675 
676     @Override
shouldExtendLifetime(NotificationEntry entry)677     public boolean shouldExtendLifetime(NotificationEntry entry) {
678         return entry != null
679                 &&(mNotificationGutsExposed != null
680                     && entry.getGuts() != null
681                     && mNotificationGutsExposed == entry.getGuts()
682                     && !mNotificationGutsExposed.isLeavebehind());
683     }
684 
685     @Override
setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend)686     public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
687         if (shouldExtend) {
688             mKeyToRemoveOnGutsClosed = entry.getKey();
689             if (Log.isLoggable(TAG, Log.DEBUG)) {
690                 Log.d(TAG, "Keeping notification because it's showing guts. " + entry.getKey());
691             }
692         } else {
693             if (mKeyToRemoveOnGutsClosed != null
694                     && mKeyToRemoveOnGutsClosed.equals(entry.getKey())) {
695                 mKeyToRemoveOnGutsClosed = null;
696                 if (Log.isLoggable(TAG, Log.DEBUG)) {
697                     Log.d(TAG, "Notification that was kept for guts was updated. "
698                             + entry.getKey());
699                 }
700             }
701         }
702     }
703 
704     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)705     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
706         pw.println("NotificationGutsManager state:");
707         pw.print("  mKeyToRemoveOnGutsClosed (legacy): ");
708         pw.println(mKeyToRemoveOnGutsClosed);
709     }
710 
711     /**
712      * @param gutsListener the listener for open and close guts events
713      */
setGutsListener(NotifGutsViewListener gutsListener)714     public void setGutsListener(NotifGutsViewListener gutsListener) {
715         mGutsListener = gutsListener;
716     }
717 
718     public interface OnSettingsClickListener {
onSettingsClick(String key)719         public void onSettingsClick(String key);
720     }
721 }
722