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