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