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