Home | History | Annotate | Download | only in statusbar
      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;
     17 
     18 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
     19 import static com.android.systemui.statusbar.NotificationRemoteInputManager
     20         .FORCE_REMOTE_INPUT_HISTORY;
     21 
     22 import android.app.Notification;
     23 import android.app.NotificationManager;
     24 import android.app.PendingIntent;
     25 import android.content.Context;
     26 import android.content.pm.ApplicationInfo;
     27 import android.content.pm.PackageManager;
     28 import android.database.ContentObserver;
     29 import android.os.Build;
     30 import android.os.PowerManager;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.os.SystemClock;
     34 import android.os.UserHandle;
     35 import android.provider.Settings;
     36 import android.service.notification.NotificationListenerService;
     37 import android.service.notification.NotificationStats;
     38 import android.service.notification.StatusBarNotification;
     39 import android.text.TextUtils;
     40 import android.util.ArraySet;
     41 import android.util.EventLog;
     42 import android.util.Log;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 
     46 import com.android.internal.annotations.VisibleForTesting;
     47 import com.android.internal.logging.MetricsLogger;
     48 import com.android.internal.statusbar.IStatusBarService;
     49 import com.android.internal.statusbar.NotificationVisibility;
     50 import com.android.internal.util.NotificationMessagingUtil;
     51 import com.android.systemui.DejankUtils;
     52 import com.android.systemui.Dependency;
     53 import com.android.systemui.Dumpable;
     54 import com.android.systemui.EventLogTags;
     55 import com.android.systemui.ForegroundServiceController;
     56 import com.android.systemui.R;
     57 import com.android.systemui.UiOffloadThread;
     58 import com.android.systemui.recents.misc.SystemServicesProxy;
     59 import com.android.systemui.statusbar.notification.InflationException;
     60 import com.android.systemui.statusbar.notification.NotificationInflater;
     61 import com.android.systemui.statusbar.notification.RowInflaterTask;
     62 import com.android.systemui.statusbar.notification.VisualStabilityManager;
     63 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     64 import com.android.systemui.statusbar.phone.StatusBar;
     65 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
     66 import com.android.systemui.statusbar.policy.HeadsUpManager;
     67 import com.android.systemui.util.leak.LeakDetector;
     68 
     69 import java.io.FileDescriptor;
     70 import java.io.PrintWriter;
     71 import java.util.ArrayList;
     72 import java.util.HashMap;
     73 import java.util.List;
     74 
     75 /**
     76  * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
     77  * It also handles tasks such as their inflation and their interaction with other
     78  * Notification.*Manager objects.
     79  */
     80 public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
     81         ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
     82         VisualStabilityManager.Callback {
     83     private static final String TAG = "NotificationEntryMgr";
     84     protected static final boolean DEBUG = false;
     85     protected static final boolean ENABLE_HEADS_UP = true;
     86     protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
     87 
     88     protected final NotificationMessagingUtil mMessagingUtil;
     89     protected final Context mContext;
     90     protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
     91     protected final NotificationClicker mNotificationClicker = new NotificationClicker();
     92     protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
     93             new ArraySet<>();
     94 
     95     // Dependencies:
     96     protected final NotificationLockscreenUserManager mLockscreenUserManager =
     97             Dependency.get(NotificationLockscreenUserManager.class);
     98     protected final NotificationGroupManager mGroupManager =
     99             Dependency.get(NotificationGroupManager.class);
    100     protected final NotificationGutsManager mGutsManager =
    101             Dependency.get(NotificationGutsManager.class);
    102     protected final NotificationRemoteInputManager mRemoteInputManager =
    103             Dependency.get(NotificationRemoteInputManager.class);
    104     protected final NotificationMediaManager mMediaManager =
    105             Dependency.get(NotificationMediaManager.class);
    106     protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
    107     protected final DeviceProvisionedController mDeviceProvisionedController =
    108             Dependency.get(DeviceProvisionedController.class);
    109     protected final VisualStabilityManager mVisualStabilityManager =
    110             Dependency.get(VisualStabilityManager.class);
    111     protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
    112     protected final ForegroundServiceController mForegroundServiceController =
    113             Dependency.get(ForegroundServiceController.class);
    114     protected final NotificationListener mNotificationListener =
    115             Dependency.get(NotificationListener.class);
    116     private final SmartReplyController mSmartReplyController =
    117             Dependency.get(SmartReplyController.class);
    118 
    119     protected IStatusBarService mBarService;
    120     protected NotificationPresenter mPresenter;
    121     protected Callback mCallback;
    122     protected PowerManager mPowerManager;
    123     protected SystemServicesProxy mSystemServicesProxy;
    124     protected NotificationListenerService.RankingMap mLatestRankingMap;
    125     protected HeadsUpManager mHeadsUpManager;
    126     protected NotificationData mNotificationData;
    127     protected ContentObserver mHeadsUpObserver;
    128     protected boolean mUseHeadsUp = false;
    129     protected boolean mDisableNotificationAlerts;
    130     protected NotificationListContainer mListContainer;
    131     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
    132     /**
    133      * Notifications with keys in this set are not actually around anymore. We kept them around
    134      * when they were canceled in response to a remote input interaction. This allows us to show
    135      * what you replied and allows you to continue typing into it.
    136      */
    137     private final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
    138 
    139 
    140     private final class NotificationClicker implements View.OnClickListener {
    141 
    142         @Override
    143         public void onClick(final View v) {
    144             if (!(v instanceof ExpandableNotificationRow)) {
    145                 Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
    146                 return;
    147             }
    148 
    149             mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
    150 
    151             final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    152             final StatusBarNotification sbn = row.getStatusBarNotification();
    153             if (sbn == null) {
    154                 Log.e(TAG, "NotificationClicker called on an unclickable notification,");
    155                 return;
    156             }
    157 
    158             // Check if the notification is displaying the menu, if so slide notification back
    159             if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
    160                 row.animateTranslateNotification(0);
    161                 return;
    162             }
    163 
    164             // Mark notification for one frame.
    165             row.setJustClicked(true);
    166             DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
    167 
    168             mCallback.onNotificationClicked(sbn, row);
    169         }
    170 
    171         public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
    172             Notification notification = sbn.getNotification();
    173             if (notification.contentIntent != null || notification.fullScreenIntent != null) {
    174                 row.setOnClickListener(this);
    175             } else {
    176                 row.setOnClickListener(null);
    177             }
    178         }
    179     }
    180 
    181     private final DeviceProvisionedController.DeviceProvisionedListener
    182             mDeviceProvisionedListener =
    183             new DeviceProvisionedController.DeviceProvisionedListener() {
    184                 @Override
    185                 public void onDeviceProvisionedChanged() {
    186                     updateNotifications();
    187                 }
    188             };
    189 
    190     public NotificationListenerService.RankingMap getLatestRankingMap() {
    191         return mLatestRankingMap;
    192     }
    193 
    194     public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
    195         mLatestRankingMap = latestRankingMap;
    196     }
    197 
    198     public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
    199         mDisableNotificationAlerts = disableNotificationAlerts;
    200         mHeadsUpObserver.onChange(true);
    201     }
    202 
    203     public void destroy() {
    204         mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
    205     }
    206 
    207     public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
    208         if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
    209             removeNotification(entry.key, getLatestRankingMap());
    210             mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
    211             if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
    212                 setLatestRankingMap(null);
    213             }
    214         } else {
    215             updateNotificationRanking(null);
    216         }
    217     }
    218 
    219     @Override
    220     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    221         pw.println("NotificationEntryManager state:");
    222         pw.print("  mPendingNotifications=");
    223         if (mPendingNotifications.size() == 0) {
    224             pw.println("null");
    225         } else {
    226             for (NotificationData.Entry entry : mPendingNotifications.values()) {
    227                 pw.println(entry.notification);
    228             }
    229         }
    230         pw.print("  mUseHeadsUp=");
    231         pw.println(mUseHeadsUp);
    232         pw.print("  mKeysKeptForRemoteInput: ");
    233         pw.println(mKeysKeptForRemoteInput);
    234     }
    235 
    236     public NotificationEntryManager(Context context) {
    237         mContext = context;
    238         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    239         mBarService = IStatusBarService.Stub.asInterface(
    240                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
    241         mMessagingUtil = new NotificationMessagingUtil(context);
    242         mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
    243         mGroupManager.setPendingEntries(mPendingNotifications);
    244     }
    245 
    246     public void setUpWithPresenter(NotificationPresenter presenter,
    247             NotificationListContainer listContainer, Callback callback,
    248             HeadsUpManager headsUpManager) {
    249         mPresenter = presenter;
    250         mCallback = callback;
    251         mNotificationData = new NotificationData(presenter);
    252         mHeadsUpManager = headsUpManager;
    253         mNotificationData.setHeadsUpManager(mHeadsUpManager);
    254         mListContainer = listContainer;
    255 
    256         mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
    257             @Override
    258             public void onChange(boolean selfChange) {
    259                 boolean wasUsing = mUseHeadsUp;
    260                 mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
    261                         && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
    262                         mContext.getContentResolver(),
    263                         Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
    264                         Settings.Global.HEADS_UP_OFF);
    265                 Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
    266                 if (wasUsing != mUseHeadsUp) {
    267                     if (!mUseHeadsUp) {
    268                         Log.d(TAG,
    269                                 "dismissing any existing heads up notification on disable event");
    270                         mHeadsUpManager.releaseAllImmediately();
    271                     }
    272                 }
    273             }
    274         };
    275 
    276         if (ENABLE_HEADS_UP) {
    277             mContext.getContentResolver().registerContentObserver(
    278                     Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
    279                     true,
    280                     mHeadsUpObserver);
    281             mContext.getContentResolver().registerContentObserver(
    282                     Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
    283                     mHeadsUpObserver);
    284         }
    285 
    286         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
    287 
    288         mHeadsUpObserver.onChange(true); // set up
    289         mOnAppOpsClickListener = mGutsManager::openGuts;
    290     }
    291 
    292     public NotificationData getNotificationData() {
    293         return mNotificationData;
    294     }
    295 
    296     public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
    297         return mGutsManager::openGuts;
    298     }
    299 
    300     @Override
    301     public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
    302         mUiOffloadThread.submit(() -> {
    303             try {
    304                 mBarService.onNotificationExpansionChanged(key, userAction, expanded);
    305             } catch (RemoteException e) {
    306                 // Ignore.
    307             }
    308         });
    309     }
    310 
    311     @Override
    312     public void onReorderingAllowed() {
    313         updateNotifications();
    314     }
    315 
    316     private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
    317         if (mPresenter.isDeviceInVrMode()) {
    318             return true;
    319         }
    320 
    321         return mNotificationData.shouldSuppressFullScreenIntent(entry);
    322     }
    323 
    324     private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
    325         PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
    326                 entry.notification.getUser().getIdentifier());
    327 
    328         final StatusBarNotification sbn = entry.notification;
    329         if (entry.row != null) {
    330             entry.reset();
    331             updateNotification(entry, pmUser, sbn, entry.row);
    332         } else {
    333             new RowInflaterTask().inflate(mContext, parent, entry,
    334                     row -> {
    335                         bindRow(entry, pmUser, sbn, row);
    336                         updateNotification(entry, pmUser, sbn, row);
    337                     });
    338         }
    339     }
    340 
    341     private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
    342             StatusBarNotification sbn, ExpandableNotificationRow row) {
    343         row.setExpansionLogger(this, entry.notification.getKey());
    344         row.setGroupManager(mGroupManager);
    345         row.setHeadsUpManager(mHeadsUpManager);
    346         row.setOnExpandClickListener(mPresenter);
    347         row.setInflationCallback(this);
    348         row.setLongPressListener(getNotificationLongClicker());
    349         mListContainer.bindRow(row);
    350         mRemoteInputManager.bindRow(row);
    351 
    352         // Get the app name.
    353         // Note that Notification.Builder#bindHeaderAppName has similar logic
    354         // but since this field is used in the guts, it must be accurate.
    355         // Therefore we will only show the application label, or, failing that, the
    356         // package name. No substitutions.
    357         final String pkg = sbn.getPackageName();
    358         String appname = pkg;
    359         try {
    360             final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
    361                     PackageManager.MATCH_UNINSTALLED_PACKAGES
    362                             | PackageManager.MATCH_DISABLED_COMPONENTS);
    363             if (info != null) {
    364                 appname = String.valueOf(pmUser.getApplicationLabel(info));
    365             }
    366         } catch (PackageManager.NameNotFoundException e) {
    367             // Do nothing
    368         }
    369         row.setAppName(appname);
    370         row.setOnDismissRunnable(() ->
    371                 performRemoveNotification(row.getStatusBarNotification()));
    372         row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
    373         if (ENABLE_REMOTE_INPUT) {
    374             row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    375         }
    376 
    377         row.setAppOpsOnClickListener(mOnAppOpsClickListener);
    378 
    379         mCallback.onBindRow(entry, pmUser, sbn, row);
    380     }
    381 
    382     public void performRemoveNotification(StatusBarNotification n) {
    383         final int rank = mNotificationData.getRank(n.getKey());
    384         final int count = mNotificationData.getActiveNotifications().size();
    385         final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
    386                 true);
    387         NotificationData.Entry entry = mNotificationData.get(n.getKey());
    388 
    389         if (FORCE_REMOTE_INPUT_HISTORY
    390                 && mKeysKeptForRemoteInput.contains(n.getKey())) {
    391             mKeysKeptForRemoteInput.remove(n.getKey());
    392         }
    393 
    394         mRemoteInputManager.onPerformRemoveNotification(n, entry);
    395         final String pkg = n.getPackageName();
    396         final String tag = n.getTag();
    397         final int id = n.getId();
    398         final int userId = n.getUserId();
    399         try {
    400             int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
    401             if (isHeadsUp(n.getKey())) {
    402                 dismissalSurface = NotificationStats.DISMISSAL_PEEK;
    403             } else if (mListContainer.hasPulsingNotifications()) {
    404                 dismissalSurface = NotificationStats.DISMISSAL_AOD;
    405             }
    406             mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface, nv);
    407             removeNotification(n.getKey(), null);
    408 
    409         } catch (RemoteException ex) {
    410             // system process is dead if we're here.
    411         }
    412 
    413         mCallback.onPerformRemoveNotification(n);
    414     }
    415 
    416     /**
    417      * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
    418      * about the failure.
    419      *
    420      * WARNING: this will call back into us.  Don't hold any locks.
    421      */
    422     void handleNotificationError(StatusBarNotification n, String message) {
    423         removeNotification(n.getKey(), null);
    424         try {
    425             mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
    426                     n.getInitialPid(), message, n.getUserId());
    427         } catch (RemoteException ex) {
    428             // The end is nigh.
    429         }
    430     }
    431 
    432     private void abortExistingInflation(String key) {
    433         if (mPendingNotifications.containsKey(key)) {
    434             NotificationData.Entry entry = mPendingNotifications.get(key);
    435             entry.abortTask();
    436             mPendingNotifications.remove(key);
    437         }
    438         NotificationData.Entry addedEntry = mNotificationData.get(key);
    439         if (addedEntry != null) {
    440             addedEntry.abortTask();
    441         }
    442     }
    443 
    444     @Override
    445     public void handleInflationException(StatusBarNotification notification, Exception e) {
    446         handleNotificationError(notification, e.getMessage());
    447     }
    448 
    449     private void addEntry(NotificationData.Entry shadeEntry) {
    450         boolean isHeadsUped = shouldPeek(shadeEntry);
    451         if (isHeadsUped) {
    452             mHeadsUpManager.showNotification(shadeEntry);
    453             // Mark as seen immediately
    454             setNotificationShown(shadeEntry.notification);
    455         }
    456         addNotificationViews(shadeEntry);
    457         mCallback.onNotificationAdded(shadeEntry);
    458     }
    459 
    460     @Override
    461     public void onAsyncInflationFinished(NotificationData.Entry entry) {
    462         mPendingNotifications.remove(entry.key);
    463         // If there was an async task started after the removal, we don't want to add it back to
    464         // the list, otherwise we might get leaks.
    465         boolean isNew = mNotificationData.get(entry.key) == null;
    466         if (isNew && !entry.row.isRemoved()) {
    467             addEntry(entry);
    468         } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
    469             mVisualStabilityManager.onLowPriorityUpdated(entry);
    470             mPresenter.updateNotificationViews();
    471         }
    472         entry.row.setLowPriorityStateUpdated(false);
    473     }
    474 
    475     @Override
    476     public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
    477         boolean deferRemoval = false;
    478         abortExistingInflation(key);
    479         if (mHeadsUpManager.isHeadsUp(key)) {
    480             // A cancel() in response to a remote input shouldn't be delayed, as it makes the
    481             // sending look longer than it takes.
    482             // Also we should not defer the removal if reordering isn't allowed since otherwise
    483             // some notifications can't disappear before the panel is closed.
    484             boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
    485                     && !FORCE_REMOTE_INPUT_HISTORY
    486                     || !mVisualStabilityManager.isReorderingAllowed();
    487             deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
    488         }
    489         mMediaManager.onNotificationRemoved(key);
    490 
    491         NotificationData.Entry entry = mNotificationData.get(key);
    492         if (FORCE_REMOTE_INPUT_HISTORY
    493                 && shouldKeepForRemoteInput(entry)
    494                 && entry.row != null && !entry.row.isDismissed()) {
    495             CharSequence remoteInputText = entry.remoteInputText;
    496             if (TextUtils.isEmpty(remoteInputText)) {
    497                 remoteInputText = entry.remoteInputTextWhenReset;
    498             }
    499             StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry,
    500                     remoteInputText, false /* showSpinner */);
    501             boolean updated = false;
    502             entry.onRemoteInputInserted();
    503             try {
    504                 updateNotificationInternal(newSbn, null);
    505                 updated = true;
    506             } catch (InflationException e) {
    507                 deferRemoval = false;
    508             }
    509             if (updated) {
    510                 Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
    511                 addKeyKeptForRemoteInput(entry.key);
    512                 return;
    513             }
    514         }
    515 
    516         if (FORCE_REMOTE_INPUT_HISTORY
    517                 && shouldKeepForSmartReply(entry)
    518                 && entry.row != null && !entry.row.isDismissed()) {
    519             // Turn off the spinner and hide buttons when an app cancels the notification.
    520             StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry);
    521             boolean updated = false;
    522             try {
    523                 updateNotificationInternal(newSbn, null);
    524                 updated = true;
    525             } catch (InflationException e) {
    526                 // Ignore just don't keep the notification around.
    527             }
    528             // Treat the reply as longer sending.
    529             mSmartReplyController.stopSending(entry);
    530             if (updated) {
    531                 Log.w(TAG, "Keeping notification around after sending smart reply " + entry.key);
    532                 addKeyKeptForRemoteInput(entry.key);
    533                 return;
    534             }
    535         }
    536 
    537         // Actually removing notification so smart reply controller can forget about it.
    538         mSmartReplyController.stopSending(entry);
    539 
    540         if (deferRemoval) {
    541             mLatestRankingMap = ranking;
    542             mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
    543             return;
    544         }
    545 
    546         if (mRemoteInputManager.onRemoveNotification(entry)) {
    547             mLatestRankingMap = ranking;
    548             return;
    549         }
    550 
    551         if (entry != null && mGutsManager.getExposedGuts() != null
    552                 && mGutsManager.getExposedGuts() == entry.row.getGuts()
    553                 && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
    554             Log.w(TAG, "Keeping notification because it's showing guts. " + key);
    555             mLatestRankingMap = ranking;
    556             mGutsManager.setKeyToRemoveOnGutsClosed(key);
    557             return;
    558         }
    559 
    560         if (entry != null) {
    561             mForegroundServiceController.removeNotification(entry.notification);
    562         }
    563 
    564         if (entry != null && entry.row != null) {
    565             entry.row.setRemoved();
    566             mListContainer.cleanUpViewState(entry.row);
    567         }
    568         // Let's remove the children if this was a summary
    569         handleGroupSummaryRemoved(key);
    570         StatusBarNotification old = removeNotificationViews(key, ranking);
    571 
    572         mCallback.onNotificationRemoved(key, old);
    573     }
    574 
    575     public StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry,
    576             CharSequence remoteInputText, boolean showSpinner) {
    577         StatusBarNotification sbn = entry.notification;
    578 
    579         Notification.Builder b = Notification.Builder
    580                 .recoverBuilder(mContext, sbn.getNotification().clone());
    581         if (remoteInputText != null) {
    582             CharSequence[] oldHistory = sbn.getNotification().extras
    583                     .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
    584             CharSequence[] newHistory;
    585             if (oldHistory == null) {
    586                 newHistory = new CharSequence[1];
    587             } else {
    588                 newHistory = new CharSequence[oldHistory.length + 1];
    589                 System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
    590             }
    591             newHistory[0] = String.valueOf(remoteInputText);
    592             b.setRemoteInputHistory(newHistory);
    593         }
    594         b.setShowRemoteInputSpinner(showSpinner);
    595         b.setHideSmartReplies(true);
    596 
    597         Notification newNotification = b.build();
    598 
    599         // Undo any compatibility view inflation
    600         newNotification.contentView = sbn.getNotification().contentView;
    601         newNotification.bigContentView = sbn.getNotification().bigContentView;
    602         newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
    603 
    604         StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
    605                 sbn.getOpPkg(),
    606                 sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
    607                 newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
    608         return newSbn;
    609     }
    610 
    611     @VisibleForTesting
    612     StatusBarNotification rebuildNotificationForCanceledSmartReplies(
    613             NotificationData.Entry entry) {
    614         return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
    615                 false /* showSpinner */);
    616     }
    617 
    618     private boolean shouldKeepForSmartReply(NotificationData.Entry entry) {
    619         return entry != null && mSmartReplyController.isSendingSmartReply(entry.key);
    620     }
    621 
    622     private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) {
    623         if (entry == null) {
    624             return false;
    625         }
    626         if (mRemoteInputManager.getController().isSpinning(entry.key)) {
    627             return true;
    628         }
    629         if (entry.hasJustSentRemoteInput()) {
    630             return true;
    631         }
    632         return false;
    633     }
    634 
    635     private StatusBarNotification removeNotificationViews(String key,
    636             NotificationListenerService.RankingMap ranking) {
    637         NotificationData.Entry entry = mNotificationData.remove(key, ranking);
    638         if (entry == null) {
    639             Log.w(TAG, "removeNotification for unknown key: " + key);
    640             return null;
    641         }
    642         updateNotifications();
    643         Dependency.get(LeakDetector.class).trackGarbage(entry);
    644         return entry.notification;
    645     }
    646 
    647     /**
    648      * Ensures that the group children are cancelled immediately when the group summary is cancelled
    649      * instead of waiting for the notification manager to send all cancels. Otherwise this could
    650      * lead to flickers.
    651      *
    652      * This also ensures that the animation looks nice and only consists of a single disappear
    653      * animation instead of multiple.
    654      *  @param key the key of the notification was removed
    655      *
    656      */
    657     private void handleGroupSummaryRemoved(String key) {
    658         NotificationData.Entry entry = mNotificationData.get(key);
    659         if (entry != null && entry.row != null
    660                 && entry.row.isSummaryWithChildren()) {
    661             if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
    662                 // We don't want to remove children for autobundled notifications as they are not
    663                 // always cancelled. We only remove them if they were dismissed by the user.
    664                 return;
    665             }
    666             List<ExpandableNotificationRow> notificationChildren =
    667                     entry.row.getNotificationChildren();
    668             for (int i = 0; i < notificationChildren.size(); i++) {
    669                 ExpandableNotificationRow row = notificationChildren.get(i);
    670                 if ((row.getStatusBarNotification().getNotification().flags
    671                         & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
    672                     // the child is a foreground service notification which we can't remove!
    673                     continue;
    674                 }
    675                 row.setKeepInParent(true);
    676                 // we need to set this state earlier as otherwise we might generate some weird
    677                 // animations
    678                 row.setRemoved();
    679             }
    680         }
    681     }
    682 
    683     public void updateNotificationsOnDensityOrFontScaleChanged() {
    684         ArrayList<NotificationData.Entry> userNotifications =
    685                 mNotificationData.getNotificationsForCurrentUser();
    686         for (int i = 0; i < userNotifications.size(); i++) {
    687             NotificationData.Entry entry = userNotifications.get(i);
    688             boolean exposedGuts = mGutsManager.getExposedGuts() != null
    689                     && entry.row.getGuts() == mGutsManager.getExposedGuts();
    690             entry.row.onDensityOrFontScaleChanged();
    691             if (exposedGuts) {
    692                 mGutsManager.onDensityOrFontScaleChanged(entry.row);
    693             }
    694         }
    695     }
    696 
    697     protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
    698             StatusBarNotification sbn, ExpandableNotificationRow row) {
    699         row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
    700         boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
    701         boolean isUpdate = mNotificationData.get(entry.key) != null;
    702         boolean wasLowPriority = row.isLowPriority();
    703         row.setIsLowPriority(isLowPriority);
    704         row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
    705         // bind the click event to the content area
    706         mNotificationClicker.register(row, sbn);
    707 
    708         // Extract target SDK version.
    709         try {
    710             ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
    711             entry.targetSdk = info.targetSdkVersion;
    712         } catch (PackageManager.NameNotFoundException ex) {
    713             Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
    714         }
    715         row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
    716                 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    717         entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    718         entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
    719 
    720         entry.row = row;
    721         entry.row.setOnActivatedListener(mPresenter);
    722 
    723         boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
    724                 mNotificationData.getImportance(sbn.getKey()));
    725         boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
    726                 && !mPresenter.isPresenterFullyCollapsed();
    727         row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
    728         row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
    729         row.updateNotification(entry);
    730     }
    731 
    732 
    733     protected void addNotificationViews(NotificationData.Entry entry) {
    734         if (entry == null) {
    735             return;
    736         }
    737         // Add the expanded view and icon.
    738         mNotificationData.add(entry);
    739         tagForeground(entry.notification);
    740         updateNotifications();
    741     }
    742 
    743     protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
    744             throws InflationException {
    745         if (DEBUG) {
    746             Log.d(TAG, "createNotificationViews(notification=" + sbn);
    747         }
    748         NotificationData.Entry entry = new NotificationData.Entry(sbn);
    749         Dependency.get(LeakDetector.class).trackInstance(entry);
    750         entry.createIcons(mContext, sbn);
    751         // Construct the expanded view.
    752         inflateViews(entry, mListContainer.getViewParentForNotification(entry));
    753         return entry;
    754     }
    755 
    756     private void addNotificationInternal(StatusBarNotification notification,
    757             NotificationListenerService.RankingMap ranking) throws InflationException {
    758         String key = notification.getKey();
    759         if (DEBUG) Log.d(TAG, "addNotification key=" + key);
    760 
    761         mNotificationData.updateRanking(ranking);
    762         NotificationData.Entry shadeEntry = createNotificationViews(notification);
    763         boolean isHeadsUped = shouldPeek(shadeEntry);
    764         if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
    765             if (shouldSuppressFullScreenIntent(shadeEntry)) {
    766                 if (DEBUG) {
    767                     Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
    768                 }
    769             } else if (mNotificationData.getImportance(key)
    770                     < NotificationManager.IMPORTANCE_HIGH) {
    771                 if (DEBUG) {
    772                     Log.d(TAG, "No Fullscreen intent: not important enough: "
    773                             + key);
    774                 }
    775             } else {
    776                 // Stop screensaver if the notification has a fullscreen intent.
    777                 // (like an incoming phone call)
    778                 SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
    779 
    780                 // not immersive & a fullscreen alert should be shown
    781                 if (DEBUG)
    782                     Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
    783                 try {
    784                     EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
    785                             key);
    786                     notification.getNotification().fullScreenIntent.send();
    787                     shadeEntry.notifyFullScreenIntentLaunched();
    788                     mMetricsLogger.count("note_fullscreen", 1);
    789                 } catch (PendingIntent.CanceledException e) {
    790                 }
    791             }
    792         }
    793         abortExistingInflation(key);
    794 
    795         mForegroundServiceController.addNotification(notification,
    796                 mNotificationData.getImportance(key));
    797 
    798         mPendingNotifications.put(key, shadeEntry);
    799         mGroupManager.onPendingEntryAdded(shadeEntry);
    800     }
    801 
    802     @VisibleForTesting
    803     protected void tagForeground(StatusBarNotification notification) {
    804         ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
    805                 notification.getUserId(), notification.getPackageName());
    806         if (activeOps != null) {
    807             int N = activeOps.size();
    808             for (int i = 0; i < N; i++) {
    809                 updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(),
    810                         notification.getPackageName(), true);
    811             }
    812         }
    813     }
    814 
    815     @Override
    816     public void addNotification(StatusBarNotification notification,
    817             NotificationListenerService.RankingMap ranking) {
    818         try {
    819             addNotificationInternal(notification, ranking);
    820         } catch (InflationException e) {
    821             handleInflationException(notification, e);
    822         }
    823     }
    824 
    825     public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) {
    826         String foregroundKey = mForegroundServiceController.getStandardLayoutKey(
    827                 UserHandle.getUserId(uid), pkg);
    828         if (foregroundKey != null) {
    829             mNotificationData.updateAppOp(appOp, uid, pkg, foregroundKey, showIcon);
    830             updateNotifications();
    831         }
    832     }
    833 
    834     private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
    835         return oldEntry == null || !oldEntry.hasInterrupted()
    836                 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
    837     }
    838 
    839     private void updateNotificationInternal(StatusBarNotification notification,
    840             NotificationListenerService.RankingMap ranking) throws InflationException {
    841         if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
    842 
    843         final String key = notification.getKey();
    844         abortExistingInflation(key);
    845         NotificationData.Entry entry = mNotificationData.get(key);
    846         if (entry == null) {
    847             return;
    848         }
    849         mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
    850         mRemoteInputManager.onUpdateNotification(entry);
    851         mSmartReplyController.stopSending(entry);
    852 
    853         if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
    854             mGutsManager.setKeyToRemoveOnGutsClosed(null);
    855             Log.w(TAG, "Notification that was kept for guts was updated. " + key);
    856         }
    857 
    858         Notification n = notification.getNotification();
    859         mNotificationData.updateRanking(ranking);
    860 
    861         final StatusBarNotification oldNotification = entry.notification;
    862         entry.notification = notification;
    863         mGroupManager.onEntryUpdated(entry, oldNotification);
    864 
    865         entry.updateIcons(mContext, notification);
    866         inflateViews(entry, mListContainer.getViewParentForNotification(entry));
    867 
    868         mForegroundServiceController.updateNotification(notification,
    869                 mNotificationData.getImportance(key));
    870 
    871         boolean shouldPeek = shouldPeek(entry, notification);
    872         boolean alertAgain = alertAgain(entry, n);
    873 
    874         updateHeadsUp(key, entry, shouldPeek, alertAgain);
    875         updateNotifications();
    876 
    877         if (!notification.isClearable()) {
    878             // The user may have performed a dismiss action on the notification, since it's
    879             // not clearable we should snap it back.
    880             mListContainer.snapViewIfNeeded(entry.row);
    881         }
    882 
    883         if (DEBUG) {
    884             // Is this for you?
    885             boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
    886             Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
    887         }
    888 
    889         mCallback.onNotificationUpdated(notification);
    890     }
    891 
    892     @Override
    893     public void updateNotification(StatusBarNotification notification,
    894             NotificationListenerService.RankingMap ranking) {
    895         try {
    896             updateNotificationInternal(notification, ranking);
    897         } catch (InflationException e) {
    898             handleInflationException(notification, e);
    899         }
    900     }
    901 
    902     public void updateNotifications() {
    903         mNotificationData.filterAndSort();
    904 
    905         mPresenter.updateNotificationViews();
    906     }
    907 
    908     public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
    909         mNotificationData.updateRanking(ranking);
    910         updateNotifications();
    911     }
    912 
    913     protected boolean shouldPeek(NotificationData.Entry entry) {
    914         return shouldPeek(entry, entry.notification);
    915     }
    916 
    917     public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
    918         if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
    919             if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
    920             return false;
    921         }
    922 
    923         if (mNotificationData.shouldFilterOut(entry)) {
    924             if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
    925             return false;
    926         }
    927 
    928         boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
    929 
    930         if (!inUse && !mPresenter.isDozing()) {
    931             if (DEBUG) {
    932                 Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
    933             }
    934             return false;
    935         }
    936 
    937         if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(entry)) {
    938             if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
    939             return false;
    940         }
    941 
    942         // Peeking triggers an ambient display pulse, so disable peek is ambient is active
    943         if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(entry)) {
    944             if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
    945             return false;
    946         }
    947 
    948         if (entry.hasJustLaunchedFullScreenIntent()) {
    949             if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
    950             return false;
    951         }
    952 
    953         if (isSnoozedPackage(sbn)) {
    954             if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
    955             return false;
    956         }
    957 
    958         // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
    959         int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
    960                 : NotificationManager.IMPORTANCE_HIGH;
    961         if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
    962             if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
    963             return false;
    964         }
    965 
    966         // Don't peek notifications that are suppressed due to group alert behavior
    967         if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
    968             if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
    969             return false;
    970         }
    971 
    972         if (!mCallback.shouldPeek(entry, sbn)) {
    973             return false;
    974         }
    975 
    976         return true;
    977     }
    978 
    979     protected void setNotificationShown(StatusBarNotification n) {
    980         setNotificationsShown(new String[]{n.getKey()});
    981     }
    982 
    983     protected void setNotificationsShown(String[] keys) {
    984         try {
    985             mNotificationListener.setNotificationsShown(keys);
    986         } catch (RuntimeException e) {
    987             Log.d(TAG, "failed setNotificationsShown: ", e);
    988         }
    989     }
    990 
    991     protected boolean isSnoozedPackage(StatusBarNotification sbn) {
    992         return mHeadsUpManager.isSnoozed(sbn.getPackageName());
    993     }
    994 
    995     protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
    996             boolean alertAgain) {
    997         final boolean wasHeadsUp = isHeadsUp(key);
    998         if (wasHeadsUp) {
    999             if (!shouldPeek) {
   1000                 // We don't want this to be interrupting anymore, lets remove it
   1001                 mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
   1002             } else {
   1003                 mHeadsUpManager.updateNotification(entry, alertAgain);
   1004             }
   1005         } else if (shouldPeek && alertAgain) {
   1006             // This notification was updated to be a heads-up, show it!
   1007             mHeadsUpManager.showNotification(entry);
   1008         }
   1009     }
   1010 
   1011     protected boolean isHeadsUp(String key) {
   1012         return mHeadsUpManager.isHeadsUp(key);
   1013     }
   1014 
   1015     public boolean isNotificationKeptForRemoteInput(String key) {
   1016         return mKeysKeptForRemoteInput.contains(key);
   1017     }
   1018 
   1019     public void removeKeyKeptForRemoteInput(String key) {
   1020         mKeysKeptForRemoteInput.remove(key);
   1021     }
   1022 
   1023     public void addKeyKeptForRemoteInput(String key) {
   1024         if (FORCE_REMOTE_INPUT_HISTORY) {
   1025             mKeysKeptForRemoteInput.add(key);
   1026         }
   1027     }
   1028 
   1029     /**
   1030      * Callback for NotificationEntryManager.
   1031      */
   1032     public interface Callback {
   1033 
   1034         /**
   1035          * Called when a new entry is created.
   1036          *
   1037          * @param shadeEntry entry that was created
   1038          */
   1039         void onNotificationAdded(NotificationData.Entry shadeEntry);
   1040 
   1041         /**
   1042          * Called when a notification was updated.
   1043          *
   1044          * @param notification notification that was updated
   1045          */
   1046         void onNotificationUpdated(StatusBarNotification notification);
   1047 
   1048         /**
   1049          * Called when a notification was removed.
   1050          *
   1051          * @param key key of notification that was removed
   1052          * @param old StatusBarNotification of the notification before it was removed
   1053          */
   1054         void onNotificationRemoved(String key, StatusBarNotification old);
   1055 
   1056 
   1057         /**
   1058          * Called when a notification is clicked.
   1059          *
   1060          * @param sbn notification that was clicked
   1061          * @param row row for that notification
   1062          */
   1063         void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
   1064 
   1065         /**
   1066          * Called when a new notification and row is created.
   1067          *
   1068          * @param entry entry for the notification
   1069          * @param pmUser package manager for user
   1070          * @param sbn notification
   1071          * @param row row for the notification
   1072          */
   1073         void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
   1074                 StatusBarNotification sbn, ExpandableNotificationRow row);
   1075 
   1076         /**
   1077          * Removes a notification immediately.
   1078          *
   1079          * @param statusBarNotification notification that is being removed
   1080          */
   1081         void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
   1082 
   1083         /**
   1084          * Returns true if NotificationEntryManager should peek this notification.
   1085          *
   1086          * @param entry entry of the notification that might be peeked
   1087          * @param sbn notification that might be peeked
   1088          * @return true if the notification should be peeked
   1089          */
   1090         boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
   1091     }
   1092 }
   1093