summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/DependencyBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java)45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java217
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java125
28 files changed, 826 insertions, 88 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9f4a4e08bbfd..689ccf7ffc36 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -55,6 +55,7 @@ import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -302,6 +303,7 @@ public class Dependency extends SystemUI {
@Inject Lazy<ChannelEditorDialogController> mChannelEditorDialogController;
@Inject Lazy<INotificationManager> mINotificationManager;
@Inject Lazy<FalsingManager> mFalsingManager;
+ @Inject Lazy<NotificationClickNotifier> mClickNotifier;
@Inject
public Dependency() {
@@ -479,6 +481,7 @@ public class Dependency extends SystemUI {
mProviders.put(ChannelEditorDialogController.class, mChannelEditorDialogController::get);
mProviders.put(INotificationManager.class, mINotificationManager::get);
mProviders.put(FalsingManager.class, mFalsingManager::get);
+ mProviders.put(NotificationClickNotifier.class, mClickNotifier::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index 057d70ccdc0d..79d8b17e6d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -70,6 +70,8 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerServiceImpl;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import dagger.Binds;
@@ -241,4 +243,9 @@ public abstract class DependencyBinder {
*/
@Binds
public abstract FalsingManager provideFalsingmanager(FalsingManagerProxy falsingManagerImpl);
+
+ /**
+ */
+ @Binds
+ public abstract SystemClock provideSystemClock(SystemClockImpl systemClock);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
index 05acdd080aa5..7db6642276f8 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -23,8 +23,12 @@ import android.os.Looper;
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
/**
* Extends the lifetime of foreground notification services such that they show for at least
@@ -39,8 +43,15 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx
private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
private Handler mHandler = new Handler(Looper.getMainLooper());
+ private final SystemClock mSystemClock;
+ private final NotificationInteractionTracker mInteractionTracker;
- public ForegroundServiceLifetimeExtender() {
+ @Inject
+ public ForegroundServiceLifetimeExtender(
+ NotificationInteractionTracker interactionTracker,
+ SystemClock systemClock) {
+ mSystemClock = systemClock;
+ mInteractionTracker = interactionTracker;
}
@Override
@@ -55,8 +66,9 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx
return false;
}
- long currentTime = System.currentTimeMillis();
- return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
+ boolean hasInteracted = mInteractionTracker.hasUserInteractedWith(entry.key);
+ long aliveTime = mSystemClock.uptimeMillis() - entry.getCreationTime();
+ return aliveTime < MIN_FGS_TIME_MS && !hasInteracted;
}
@Override
@@ -84,7 +96,7 @@ public class ForegroundServiceLifetimeExtender implements NotificationLifetimeEx
}
};
long delayAmt = MIN_FGS_TIME_MS
- - (System.currentTimeMillis() - entry.notification.getPostTime());
+ - (mSystemClock.uptimeMillis() - entry.getCreationTime());
mHandler.postDelayed(r, delayAmt);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 0162deb55143..1e1eaf3e2cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -44,7 +44,8 @@ public class ForegroundServiceNotificationListener {
@Inject
public ForegroundServiceNotificationListener(Context context,
ForegroundServiceController foregroundServiceController,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager,
+ ForegroundServiceLifetimeExtender fgsLifetimeExtender) {
mContext = context;
mForegroundServiceController = foregroundServiceController;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@@ -66,9 +67,7 @@ public class ForegroundServiceNotificationListener {
removeNotification(entry.notification);
}
});
-
- notificationEntryManager.addNotificationLifetimeExtender(
- new ForegroundServiceLifetimeExtender());
+ notificationEntryManager.addNotificationLifetimeExtender(fgsLifetimeExtender);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index d815d95f23d5..9900a93971f8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -167,8 +168,9 @@ public class SystemUIFactory {
@Singleton
@Provides
public NotificationLockscreenUserManager provideNotificationLockscreenUserManager(
- Context context) {
- return new NotificationLockscreenUserManagerImpl(context);
+ Context context,
+ NotificationClickNotifier clickNotifier) {
+ return new NotificationLockscreenUserManagerImpl(context, clickNotifier);
}
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
new file mode 100644
index 000000000000..0d3948853cda
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -0,0 +1,90 @@
+package com.android.systemui.statusbar
+
+import android.app.Notification
+import android.os.Handler
+import android.os.RemoteException
+
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.Dependency
+import com.android.systemui.util.Assert
+
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Singleton
+
+/**
+ * Class to shim calls to IStatusBarManager#onNotificationClick/#onNotificationActionClick that
+ * allow an in-process notification to go out (e.g., for tracking interactions) as well as
+ * sending the messages along to system server.
+ *
+ * NOTE: this class eats exceptions from system server, as no current client of these APIs cares
+ * about errors
+ */
+@Singleton
+public class NotificationClickNotifier @Inject constructor(
+ val barService: IStatusBarService,
+ @Named(Dependency.MAIN_HANDLER_NAME) val mainHandler: Handler
+) {
+ val listeners = mutableListOf<NotificationInteractionListener>()
+
+ fun addNotificationInteractionListener(listener: NotificationInteractionListener) {
+ Assert.isMainThread()
+ listeners.add(listener)
+ }
+
+ fun removeNotificationInteractionListener(listener: NotificationInteractionListener) {
+ Assert.isMainThread()
+ listeners.remove(listener)
+ }
+
+ private fun notifyListenersAboutInteraction(key: String) {
+ for (l in listeners) {
+ l.onNotificationInteraction(key)
+ }
+ }
+
+ fun onNotificationActionClick(
+ key: String,
+ actionIndex: Int,
+ action: Notification.Action,
+ visibility: NotificationVisibility,
+ generatedByAssistant: Boolean
+ ) {
+ try {
+ barService.onNotificationActionClick(
+ key, actionIndex, action, visibility, generatedByAssistant)
+ } catch (e: RemoteException) {
+ // nothing
+ }
+
+ mainHandler.post {
+ notifyListenersAboutInteraction(key)
+ }
+ }
+
+ fun onNotificationClick(
+ key: String,
+ visibility: NotificationVisibility
+ ) {
+ try {
+ barService.onNotificationClick(key, visibility)
+ } catch (e: RemoteException) {
+ // nothing
+ }
+
+ mainHandler.post {
+ notifyListenersAboutInteraction(key)
+ }
+ }
+}
+
+/**
+ * Interface for listeners to get notified when a notification is interacted with via a click or
+ * interaction with remote input or actions
+ */
+interface NotificationInteractionListener {
+ fun onNotificationInteraction(key: String)
+}
+
+private const val TAG = "NotificationClickNotifier"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
new file mode 100644
index 000000000000..40a3ed64f2c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
@@ -0,0 +1,45 @@
+package com.android.systemui.statusbar
+
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.NotificationEntryListener
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class to track user interaction with notifications. It's a glorified map of key : bool that can
+ * merge multiple "user interacted with notification" signals into a single place.
+ */
+@Singleton
+class NotificationInteractionTracker @Inject constructor(
+ private val clicker: NotificationClickNotifier,
+ private val entryManager: NotificationEntryManager
+) : NotificationEntryListener, NotificationInteractionListener {
+ private val interactions = mutableMapOf<String, Boolean>()
+
+ init {
+ clicker.addNotificationInteractionListener(this)
+ entryManager.addNotificationEntryListener(this)
+ }
+
+ fun hasUserInteractedWith(key: String): Boolean = key in interactions
+
+ override fun onNotificationAdded(entry: NotificationEntry) {
+ interactions[entry.key] = false
+ }
+
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean
+ ) {
+ interactions.remove(entry.key)
+ }
+
+ override fun onNotificationInteraction(key: String) {
+ interactions[key] = true
+ }
+}
+
+private const val TAG = "NotificationInteractionTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e08a5ae07bd8..67218fc30976 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -28,8 +28,6 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -37,7 +35,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -80,8 +77,8 @@ public class NotificationLockscreenUserManagerImpl implements
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
private final UserManager mUserManager;
- private final IStatusBarService mBarService;
private final List<UserChangedListener> mListeners = new ArrayList<>();
+ private final NotificationClickNotifier mClickNotifier;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -146,11 +143,7 @@ public class NotificationLockscreenUserManagerImpl implements
getEntryManager().getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
rank, count, true, location);
- try {
- mBarService.onNotificationClick(notificationKey, nv);
- } catch (RemoteException e) {
- /* ignore */
- }
+ mClickNotifier.onNotificationClick(notificationKey, nv);
}
}
}
@@ -171,15 +164,16 @@ public class NotificationLockscreenUserManagerImpl implements
return mEntryManager;
}
- public NotificationLockscreenUserManagerImpl(Context context) {
+ public NotificationLockscreenUserManagerImpl(
+ Context context,
+ NotificationClickNotifier clickNotifier) {
mContext = context;
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mCurrentUserId = ActivityManager.getCurrentUser();
- mBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Dependency.get(StatusBarStateController.class).addCallback(this);
+ mClickNotifier = clickNotifier;
mLockPatternUtils = new LockPatternUtils(context);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 1440803f1524..3898ef754ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -121,6 +121,7 @@ public class NotificationRemoteInputManager implements Dumpable {
protected final Context mContext;
private final UserManager mUserManager;
private final KeyguardManager mKeyguardManager;
+ private final NotificationClickNotifier mClickNotifier;
protected RemoteInputController mRemoteInputController;
protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
@@ -203,11 +204,7 @@ public class NotificationRemoteInputManager implements Dumpable {
mEntryManager.getNotificationData().get(key));
final NotificationVisibility nv =
NotificationVisibility.obtain(key, rank, count, true, location);
- try {
- mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
- } catch (RemoteException e) {
- // Ignore
- }
+ mClickNotifier.onNotificationActionClick(key, buttonIndex, action, nv, false);
}
private StatusBarNotification getNotificationForParent(ViewParent parent) {
@@ -259,7 +256,8 @@ public class NotificationRemoteInputManager implements Dumpable {
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ NotificationClickNotifier clickNotifier) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
@@ -271,6 +269,7 @@ public class NotificationRemoteInputManager implements Dumpable {
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
addLifetimeExtenders();
mKeyguardManager = context.getSystemService(KeyguardManager.class);
+ mClickNotifier = clickNotifier;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 736b9ebea5c3..2a1f864dfd67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -38,14 +38,17 @@ import javax.inject.Singleton;
public class SmartReplyController {
private final IStatusBarService mBarService;
private final NotificationEntryManager mEntryManager;
+ private final NotificationClickNotifier mClickNotifier;
private Set<String> mSendingKeys = new ArraySet<>();
private Callback mCallback;
@Inject
public SmartReplyController(NotificationEntryManager entryManager,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier) {
mBarService = statusBarService;
mEntryManager = entryManager;
+ mClickNotifier = clickNotifier;
}
public void setCallback(Callback callback) {
@@ -79,12 +82,8 @@ public class SmartReplyController {
NotificationLogger.getNotificationLocation(entry);
final NotificationVisibility nv = NotificationVisibility.obtain(
entry.key, rank, count, true, location);
- try {
- mBarService.onNotificationActionClick(
- entry.key, actionIndex, action, nv, generatedByAssistant);
- } catch (RemoteException e) {
- // Nothing to do, system going down
- }
+ mClickNotifier.onNotificationActionClick(
+ entry.key, actionIndex, action, nv, generatedByAssistant);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 1aa6bc9ae5f9..848f1a0d8fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -20,6 +20,8 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
+import androidx.annotation.NonNull;
+
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
@@ -96,7 +98,7 @@ public interface NotificationEntryListener {
* @param removedByUser true if the notification was removed by a user action
*/
default void onEntryRemoved(
- NotificationEntry entry,
+ @NonNull NotificationEntry entry,
@Nullable NotificationVisibility visibility,
boolean removedByUser) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index cfc1a5f2ef3d..e50c02b66604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -21,6 +21,7 @@ import static android.service.notification.NotificationListenerService.REASON_ER
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
+import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
@@ -385,7 +386,10 @@ public class NotificationEntryManager implements
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
- NotificationEntry entry = new NotificationEntry(notification, ranking);
+ NotificationEntry entry = new NotificationEntry(
+ notification,
+ ranking,
+ SystemClock.uptimeMillis());
Dependency.get(LeakDetector.class).trackInstance(entry);
// Construct the expanded view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index d157f06c03e9..10e37591eebe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -28,6 +28,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -96,6 +97,7 @@ public final class NotificationEntry {
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
public StatusBarIconView centeredIcon;
+ private long mCreationTime;
private boolean interruption;
public boolean autoRedacted; // whether the redacted notification was generated by us
public int targetSdk;
@@ -148,6 +150,46 @@ public final class NotificationEntry {
*/
private boolean hasSentReply;
+
+ /**
+ * @param sbn the StatusBarNotification from system server
+ * @param creationTime SystemClock.uptimeMillis of when we were created
+ */
+ public NotificationEntry(
+ @NonNull StatusBarNotification sbn,
+ long creationTime) {
+ this(sbn, null, creationTime);
+ }
+
+ public NotificationEntry(
+ @NonNull StatusBarNotification sbn,
+ @Nullable NotificationListenerService.Ranking ranking,
+ long creationTime
+ ) {
+
+ mCreationTime = creationTime;
+ this.key = sbn.getKey();
+ this.notification = sbn;
+
+ if (ranking != null) {
+ populateFromRanking(ranking);
+ }
+ }
+
+ /**
+ * This method exists _only_ for tests that don't know how to pass in a creation time, and
+ * before a NotificationEntry builder was introduced for testing.
+ *
+ * It will always set SystemClock.uptimeMillis() as the creation time
+ *
+ * @param sbn the StatusBarNotification from system server
+ *
+ * @VisibleForTesting
+ */
+ public NotificationEntry(@NonNull StatusBarNotification sbn) {
+ this(sbn, null, SystemClock.uptimeMillis());
+ }
+
/**
* Whether this notification has been approved globally, at the app level, and at the channel
* level for bubbling.
@@ -168,6 +210,21 @@ public final class NotificationEntry {
private boolean mUserDismissedBubble;
/**
+ * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless
+ * of any changes to the data presented. It is set once on creation and will never change, and
+ * allows us to know exactly how long this notification has been alive for in our listener
+ * service. It is entirely unrelated to the information inside of the notification.
+ *
+ * This is different to Notification#when because it persists throughout updates, whereas
+ * system server treats every single call to notify() as a new notification and we handle
+ * updates to NotificationEntry locally.
+ */
+ @CurrentTimeMillisLong
+ public long getCreationTime() {
+ return mCreationTime;
+ }
+
+ /**
* Whether this notification is shown to the user as a high priority notification: visible on
* the lock screen/status bar and in the top section in the shade.
*/
@@ -175,20 +232,6 @@ public final class NotificationEntry {
private boolean mIsTopBucket;
- public NotificationEntry(StatusBarNotification n) {
- this(n, null);
- }
-
- public NotificationEntry(
- StatusBarNotification n,
- @Nullable NotificationListenerService.Ranking ranking) {
- this.key = n.getKey();
- this.notification = n;
- if (ranking != null) {
- populateFromRanking(ranking);
- }
- }
-
public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
channel = ranking.getChannel();
lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2c305dff3246..9d0bb707832d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -181,6 +181,7 @@ import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -595,6 +596,7 @@ public class StatusBar extends SystemUI implements DemoMode,
};
private ActivityIntentHelper mActivityIntentHelper;
private ShadeController mShadeController;
+ private NotificationClickNotifier mClickNotifier;
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -612,6 +614,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void start() {
+ mClickNotifier = Dependency.get(NotificationClickNotifier.class);
mGroupManager = Dependency.get(NotificationGroupManager.class);
mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
@@ -1073,7 +1076,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationActivityStarter = new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManager, mNotificationPanel, mPresenter, mEntryManager,
mHeadsUpManager, activityStarter, mActivityLaunchAnimator,
- mBarService, mStatusBarStateController, mKeyguardManager, mDreamManager,
+ mClickNotifier, mStatusBarStateController, mKeyguardManager, mDreamManager,
mRemoteInputManager, mStatusBarRemoteInputCallback, mGroupManager,
mLockscreenUserManager, mShadeController, mKeyguardMonitor,
mNotificationInterruptionStateProvider, mMetricsLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e00d439dc1c7..8609c4fc6070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -42,7 +42,6 @@ import android.util.Log;
import android.view.RemoteAnimationAdapter;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
@@ -54,6 +53,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -97,7 +97,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final HeadsUpManagerPhone mHeadsUpManager;
private final KeyguardManager mKeyguardManager;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final IStatusBarService mBarService;
+ private final NotificationClickNotifier mClickNotifier;
private final CommandQueue mCommandQueue;
private final IDreamManager mDreamManager;
private final Handler mMainThreadHandler;
@@ -116,7 +116,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
ActivityLaunchAnimator activityLaunchAnimator,
- IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier,
StatusBarStateController statusBarStateController,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
@@ -138,7 +138,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mPresenter = presenter;
mHeadsUpManager = headsUpManager;
mActivityLaunchAnimator = activityLaunchAnimator;
- mBarService = statusBarService;
+ mClickNotifier = clickNotifier;
mCommandQueue = commandQueue;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
@@ -334,11 +334,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mEntryManager.getNotificationData().get(notificationKey));
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
rank, count, true, location);
- try {
- mBarService.onNotificationClick(notificationKey, nv);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
+ mClickNotifier.onNotificationClick(notificationKey, nv);
+
if (!isBubble) {
if (parentToCancelFinal != null) {
removeNotification(parentToCancelFinal);
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java
new file mode 100644
index 000000000000..c594621552e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sub-class of {@link Executor} that allows Runnables to be delayed and/or cancelled.
+ */
+public interface DelayableExecutor extends Executor {
+ /**
+ * Execute supplied Runnable on the Executors thread after a specified delay.
+ *
+ * See {@link android.os.Handler#postDelayed(Runnable, long)}.
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ default Runnable executeDelayed(Runnable r, long delayMillis) {
+ return executeDelayed(r, delayMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Execute supplied Runnable on the Executors thread after a specified delay.
+ *
+ * See {@link android.os.Handler#postDelayed(Runnable, long)}.
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue..
+ */
+ Runnable executeDelayed(Runnable r, long delay, TimeUnit unit);
+
+ /**
+ * Execute supplied Runnable on the Executors thread at a specified uptime.
+ *
+ * See {@link android.os.Handler#postAtTime(Runnable, long)}.
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ default Runnable executeAtTime(Runnable r, long uptime) {
+ return executeAtTime(r, uptime, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Execute supplied Runnable on the Executors thread at a specified uptime.
+ *
+ * See {@link android.os.Handler#postAtTime(Runnable, long)}.
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit);
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
new file mode 100644
index 000000000000..26c77b00cae8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.time;
+
+/**
+ * Testable wrapper around {@link android.os.SystemClock}.
+ *
+ * Dagger can inject this wrapper into your classes. The implementation just proxies calls to the
+ * real SystemClock.
+ *
+ * In tests, pass an instance of FakeSystemClock, which allows you to control the values returned by
+ * the various getters below.
+ */
+public interface SystemClock {
+ /** @see android.os.SystemClock#uptimeMillis() */
+ long uptimeMillis();
+
+ /** @see android.os.SystemClock#elapsedRealtime() */
+ long elapsedRealtime();
+
+ /** @see android.os.SystemClock#elapsedRealtimeNanos() */
+ long elapsedRealtimeNanos();
+
+ /** @see android.os.SystemClock#currentThreadTimeMillis() */
+ long currentThreadTimeMillis();
+
+ /** @see System#currentTimeMillis() */
+ long currentTimeMillis();
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
new file mode 100644
index 000000000000..501da990d0ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.time;
+
+import javax.inject.Inject;
+
+/** Default implementation of {@link SystemClock}. */
+public class SystemClockImpl implements SystemClock {
+ @Inject
+ public SystemClockImpl() {}
+
+ @Override
+ public long uptimeMillis() {
+ return android.os.SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public long elapsedRealtime() {
+ return android.os.SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public long elapsedRealtimeNanos() {
+ return android.os.SystemClock.elapsedRealtimeNanos();
+ }
+
+ @Override
+ public long currentThreadTimeMillis() {
+ return android.os.SystemClock.currentThreadTimeMillis();
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 8a6ee12d7068..c1f94a5cefd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,7 +62,8 @@ public class ForegroundServiceControllerTest extends SysuiTestCase {
mFsc = new ForegroundServiceController();
NotificationEntryManager notificationEntryManager = mock(NotificationEntryManager.class);
mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, notificationEntryManager);
+ mContext, mFsc, notificationEntryManager,
+ mock(ForegroundServiceLifetimeExtender.class));
ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
ArgumentCaptor.forClass(NotificationEntryListener.class);
verify(notificationEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index b1dabdda2241..6dbe1b34c112 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceLifetimeExtenderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -20,63 +20,84 @@ import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIM
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.Notification;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class ForegroundServiceLifetimeExtenderTest extends SysuiTestCase {
- private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
- private StatusBarNotification mSbn;
+public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+
+ private ForegroundServiceLifetimeExtender mExtender;
private NotificationEntry mEntry;
+ private StatusBarNotification mSbn;
private Notification mNotif;
+ private final FakeSystemClock mClock = new FakeSystemClock();
+
+ @Mock
+ private NotificationInteractionTracker mInteractionTracker;
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mExtender = new ForegroundServiceLifetimeExtender(mInteractionTracker, mClock);
+
mNotif = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text")
.build();
- mSbn = mock(StatusBarNotification.class);
- when(mSbn.getNotification()).thenReturn(mNotif);
-
- mEntry = new NotificationEntry(mSbn);
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+ 0, mNotif, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+ mEntry = new NotificationEntry(mSbn, mClock.uptimeMillis());
}
+ /**
+ * ForegroundServiceLifetimeExtenderTest
+ */
@Test
public void testShouldExtendLifetime_should_foreground() {
// Extend the lifetime of a FGS notification iff it has not been visible
// for the minimum time
mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis());
+
+ // No time has elapsed, keep showing
assertTrue(mExtender.shouldExtendLifetime(mEntry));
}
@Test
public void testShouldExtendLifetime_shouldNot_foreground() {
mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
+
+ // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+ mClock.advanceTime(MIN_FGS_TIME_MS + 1);
assertFalse(mExtender.shouldExtendLifetime(mEntry));
}
@Test
public void testShouldExtendLifetime_shouldNot_notForeground() {
mNotif.flags = 0;
- when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
+
+ // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+ mClock.advanceTime(MIN_FGS_TIME_MS + 1);
assertFalse(mExtender.shouldExtendLifetime(mEntry));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 57dd8c94c790..c66114b29ee1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -71,6 +71,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Mock private NotificationData mNotificationData;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarKeyguardViewManager mKeyguardViewManager;
+ @Mock private NotificationClickNotifier mClickNotifier;
private int mCurrentUserId;
private TestNotificationLockscreenUserManager mLockscreenUserManager;
@@ -185,7 +186,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private class TestNotificationLockscreenUserManager
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
- super(context);
+ super(context, mClickNotifier);
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index b81e04821463..fd91e4823a39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -54,6 +54,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
@Mock private SmartReplyController mSmartReplyController;
@Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
+ @Mock private NotificationClickNotifier mClickNotifier;
// Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@@ -73,7 +74,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext,
mLockscreenUserManager, mSmartReplyController, mEntryManager,
() -> mock(ShadeController.class),
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()),
+ mClickNotifier);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
mEntry = new NotificationEntry(mSbn);
@@ -202,9 +204,10 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
- Handler mainHandler) {
+ Handler mainHandler,
+ NotificationClickNotifier clickNotifier) {
super(context, lockscreenUserManager, smartReplyController, notificationEntryManager,
- shadeController, mainHandler);
+ shadeController, mainHandler, clickNotifier);
}
public void setUpWithPresenterForTest(Callback callback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 81e373a8be27..8f1b6d3bb7f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -70,6 +70,7 @@ public class SmartReplyControllerTest extends SysuiTestCase {
@Mock private StatusBarNotification mSbn;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private IStatusBarService mIStatusBarService;
+ @Mock private NotificationClickNotifier mClickNotifier;
@Before
public void setUp() {
@@ -78,14 +79,15 @@ public class SmartReplyControllerTest extends SysuiTestCase {
mNotificationEntryManager);
mSmartReplyController = new SmartReplyController(mNotificationEntryManager,
- mIStatusBarService);
+ mIStatusBarService, mClickNotifier);
mDependency.injectTestDependency(SmartReplyController.class,
mSmartReplyController);
mRemoteInputManager = new NotificationRemoteInputManager(mContext,
mock(NotificationLockscreenUserManager.class), mSmartReplyController,
mNotificationEntryManager, () -> mock(ShadeController.class),
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()),
+ mClickNotifier);
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index f629757e4c68..76713e4015b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -54,6 +54,7 @@ import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.os.Process;
+import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -339,7 +340,10 @@ public class NotificationDataTest extends SysuiTestCase {
when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
NotificationEntry entry =
- new NotificationEntry(mMockStatusBarNotification, ranking);
+ new NotificationEntry(
+ mMockStatusBarNotification,
+ ranking,
+ SystemClock.uptimeMillis());
assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
assertEquals(NOTIFICATION_CHANNEL, entry.channel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index cca9f2834e93..258ddf3c98a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -30,6 +30,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +49,7 @@ public class NotificationEntryTest extends SysuiTestCase {
private NotificationEntry mEntry;
private Bundle mExtras;
+ private final FakeSystemClock mClock = new FakeSystemClock();
@Before
public void setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 06d76ebcff28..8ec6a65d4c10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -48,7 +48,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
@@ -58,6 +57,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -94,7 +94,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private IStatusBarService mStatusBarService;
+ private NotificationClickNotifier mClickNotifier;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -165,7 +165,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(),
mock(CommandQueue.class), mAssistManager, mock(NotificationPanelView.class),
mock(NotificationPresenter.class), mEntryManager, mock(HeadsUpManagerPhone.class),
- mActivityStarter, mock(ActivityLaunchAnimator.class), mStatusBarService,
+ mActivityStarter, mock(ActivityLaunchAnimator.class),
+ mClickNotifier,
mock(StatusBarStateController.class), mock(KeyguardManager.class),
mock(IDreamManager.class), mRemoteInputManager,
mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
@@ -222,7 +223,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// Notification is removed due to FLAG_AUTO_CANCEL
@@ -248,7 +249,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
@@ -278,7 +279,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
@@ -308,7 +309,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
new file mode 100644
index 000000000000..42e5a5eab6bf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import com.android.systemui.util.time.FakeSystemClock;
+
+import java.util.Collections;
+import java.util.PriorityQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class FakeExecutor implements DelayableExecutor {
+ private final FakeSystemClock mClock;
+ private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
+ private boolean mIgnoreClockUpdates;
+
+ /**
+ * Initializes a fake executor.
+ *
+ * @param clock FakeSystemClock allowing control over delayed runnables. It is strongly
+ * recommended that this clock have its auto-increment setting set to false to
+ * prevent unexpected advancement of the time.
+ */
+ public FakeExecutor(FakeSystemClock clock) {
+ mClock = clock;
+ mClock.addListener(() -> {
+ if (!mIgnoreClockUpdates) {
+ runAllReady();
+ }
+ });
+ }
+
+ /**
+ * Runs a single runnable if it's scheduled to run according to the internal clock.
+ *
+ * If constructed to advance the clock automatically, this will advance the clock enough to
+ * run the next pending item.
+ *
+ * This method does not advance the clock past the item that was run.
+ *
+ * @return Returns true if an item was run.
+ */
+ public boolean runNextReady() {
+ if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) {
+ mQueuedRunnables.poll().mRunnable.run();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Runs all Runnables that are scheduled to run according to the internal clock.
+ *
+ * If constructed to advance the clock automatically, this will advance the clock enough to
+ * run all the pending items. This method does not advance the clock past items that were
+ * run. It is equivalent to calling {@link #runNextReady()} in a loop.
+ *
+ * @return Returns the number of items that ran.
+ */
+ public int runAllReady() {
+ int num = 0;
+ while (runNextReady()) {
+ num++;
+ }
+
+ return num;
+ }
+
+ /**
+ * Advances the internal clock to the next item to run.
+ *
+ * The clock will only move forward. If the next item is set to run in the past or there is no
+ * next item, the clock does not change.
+ *
+ * Note that this will cause one or more items to actually run.
+ *
+ * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance.
+ */
+ public long advanceClockToNext() {
+ if (mQueuedRunnables.isEmpty()) {
+ return 0;
+ }
+
+ long startTime = mClock.uptimeMillis();
+ long nextTime = mQueuedRunnables.peek().mWhen;
+ if (nextTime <= startTime) {
+ return 0;
+ }
+ updateClock(nextTime);
+
+ return nextTime - startTime;
+ }
+
+
+ /**
+ * Advances the internal clock to the last item to run.
+ *
+ * The clock will only move forward. If the last item is set to run in the past or there is no
+ * next item, the clock does not change.
+ *
+ * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance.
+ */
+ public long advanceClockToLast() {
+ if (mQueuedRunnables.isEmpty()) {
+ return 0;
+ }
+
+ long startTime = mClock.uptimeMillis();
+ long nextTime = Collections.max(mQueuedRunnables).mWhen;
+ if (nextTime <= startTime) {
+ return 0;
+ }
+
+ updateClock(nextTime);
+
+ return nextTime - startTime;
+ }
+
+ /**
+ * Returns the number of un-executed runnables waiting to run.
+ */
+ public int numPending() {
+ return mQueuedRunnables.size();
+ }
+
+ @Override
+ public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
+ if (delay < 0) {
+ delay = 0;
+ }
+ return executeAtTime(r, mClock.uptimeMillis() + unit.toMillis(delay));
+ }
+
+ @Override
+ public Runnable executeAtTime(Runnable r, long uptime, TimeUnit unit) {
+ long uptimeMillis = unit.toMillis(uptime);
+
+ QueuedRunnable container = new QueuedRunnable(r, uptimeMillis);
+
+ mQueuedRunnables.offer(container);
+
+ return () -> mQueuedRunnables.remove(container);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ executeDelayed(command, 0);
+ }
+
+ /**
+ * Run all Executors in a loop until they all report they have no ready work to do.
+ *
+ * Useful if you have Executors the post work to other Executors, and you simply want to
+ * run them all until they stop posting work.
+ */
+ public static void exhaustExecutors(FakeExecutor ...executors) {
+ boolean didAnything;
+ do {
+ didAnything = false;
+ for (FakeExecutor executor : executors) {
+ didAnything = didAnything || executor.runAllReady() != 0;
+ }
+ } while (didAnything);
+ }
+
+ private void updateClock(long nextTime) {
+ mIgnoreClockUpdates = true;
+ mClock.setUptimeMillis(nextTime);
+ mIgnoreClockUpdates = false;
+ }
+
+ private static class QueuedRunnable implements Comparable<QueuedRunnable> {
+ private static AtomicInteger sCounter = new AtomicInteger();
+
+ Runnable mRunnable;
+ long mWhen;
+ private int mCounter;
+
+ private QueuedRunnable(Runnable r, long when) {
+ mRunnable = r;
+ mWhen = when;
+
+ // PrioirityQueue orders items arbitrarily when equal. We want to ensure that
+ // otherwise-equal elements are ordered according to their insertion order. Because this
+ // class only is constructed right before insertion, we use a static counter to track
+ // insertion order of otherwise equal elements.
+ mCounter = sCounter.incrementAndGet();
+ }
+
+ @Override
+ public int compareTo(QueuedRunnable other) {
+ long diff = mWhen - other.mWhen;
+
+ if (diff == 0) {
+ return mCounter - other.mCounter;
+ }
+
+ return diff > 0 ? 1 : -1;
+ }
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
new file mode 100644
index 000000000000..181636f4e7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.time;
+
+import com.android.systemui.util.concurrency.FakeExecutor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fake {@link SystemClock} for use with {@link FakeExecutor}.
+ *
+ * Attempts to simulate the behavior of a real system clock. Time can be moved forward but not
+ * backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
+ *
+ * Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
+ * every call to {@link #advanceTime}. Thread time always lags by 50% of the uptime
+ * advancement to simulate time loss due to scheduling.
+ */
+public class FakeSystemClock implements SystemClock {
+ private long mUptimeMillis = 10000;
+ private long mElapsedRealtime = 10000;
+ private long mCurrentThreadTimeMillis = 10000;
+ private long mCurrentTimeMillis = 1555555500000L;
+ private final List<ClockTickListener> mListeners = new ArrayList<>();
+
+ @Override
+ public long uptimeMillis() {
+ return mUptimeMillis;
+ }
+
+ @Override
+ public long elapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ @Override
+ public long elapsedRealtimeNanos() {
+ return mElapsedRealtime * 1000000 + 447;
+ }
+
+ @Override
+ public long currentThreadTimeMillis() {
+ return mCurrentThreadTimeMillis;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTimeMillis;
+ }
+
+ public void setUptimeMillis(long uptime) {
+ advanceTime(uptime - mUptimeMillis);
+ }
+
+ public void setCurrentTimeMillis(long millis) {
+ mCurrentTimeMillis = millis;
+ }
+
+ /**
+ * Advances the time tracked by the fake clock and notifies any listeners that the time has
+ * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+ *
+ * All tracked times increment by [millis], with the exception of currentThreadTimeMillis,
+ * which advances by [millis] * 0.5
+ */
+ public void advanceTime(long millis) {
+ advanceTime(millis, 0);
+ }
+
+ /**
+ * Advances the time tracked by the fake clock and notifies any listeners that the time has
+ * changed (for example, an attached {@link FakeExecutor} may fire its pending runnables).
+ *
+ * The tracked times change as follows:
+ * - uptimeMillis increments by [awakeMillis]
+ * - currentThreadTimeMillis increments by [awakeMillis] * 0.5
+ * - elapsedRealtime increments by [awakeMillis] + [sleepMillis]
+ * - currentTimeMillis increments by [awakeMillis] + [sleepMillis]
+ */
+ public void advanceTime(long awakeMillis, long sleepMillis) {
+ if (awakeMillis < 0 || sleepMillis < 0) {
+ throw new IllegalArgumentException("Time cannot go backwards");
+ }
+
+ if (awakeMillis > 0 || sleepMillis > 0) {
+ mUptimeMillis += awakeMillis;
+ mElapsedRealtime += awakeMillis + sleepMillis;
+ mCurrentTimeMillis += awakeMillis + sleepMillis;
+
+ mCurrentThreadTimeMillis += Math.ceil(awakeMillis * 0.5);
+
+ for (ClockTickListener listener : mListeners) {
+ listener.onClockTick();
+ }
+ }
+ }
+
+ public void addListener(ClockTickListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(ClockTickListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public interface ClockTickListener {
+ void onClockTick();
+ }
+}
+