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