Report start and end for NLS binder calls
Adding explicit start and end signals for the processing of NLS
callbacks. Since the callbacks are made on the app's main thread, added
a grace period of 10 seconds for the app to allow execution.
Flag: android.service.notification.report_nls_start_and_end
Test: atest FrameworksUiServicesNotificationTests:ManagedServicesTest
Test: atest\
FrameworksUiServicesNotificationTests:NotificationListenerWrapperTest
Test: atest\
FrameworksUiServicesNotificationTests:NotificationListenersTest
Bug: 344050265
Change-Id: If27148379c9e5f86f53923f735c7c397ea965812
diff --git a/core/java/android/service/notification/IDispatchCompletionListener.aidl b/core/java/android/service/notification/IDispatchCompletionListener.aidl
new file mode 100644
index 0000000..d89254c
--- /dev/null
+++ b/core/java/android/service/notification/IDispatchCompletionListener.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2025, 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 android.service.notification;
+
+import android.os.IBinder;
+
+/**
+ * Callback from app into system process to indicate that the "dispatch" of a notification
+ * event (by calling the corresponding NotificationListenerService API method on the main thread)
+ * was completed or there was an error that resulted in no dispatch. This does not signal that the
+ * app completed processing the event. It may still be doing that off the main thread.
+ * {@hide}
+ */
+interface IDispatchCompletionListener {
+ void notifyDispatchComplete(in long dispatchToken);
+}
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5471048..d05d6db 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -23,6 +23,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationStats;
+import android.service.notification.IDispatchCompletionListener;
import android.service.notification.IStatusBarNotificationHolder;
import android.service.notification.StatusBarNotification;
import android.service.notification.NotificationRankingUpdate;
@@ -31,24 +32,27 @@
oneway interface INotificationListener
{
// listeners and assistant
- void onListenerConnected(in NotificationRankingUpdate update);
+ void onListenerConnected(in NotificationRankingUpdate update,
+ in IDispatchCompletionListener completionCallback, in long dispatchToken);
void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder,
- in NotificationRankingUpdate update);
+ in NotificationRankingUpdate update, in long dispatchToken);
void onNotificationPostedFull(in StatusBarNotification sbn,
- in NotificationRankingUpdate update);
- void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons);
+ in NotificationRankingUpdate update, in long dispatchToken);
+ void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons, in long dispatchToken);
// stats only for assistant
void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder,
- in NotificationRankingUpdate update, in NotificationStats stats, int reason);
+ in NotificationRankingUpdate update, in NotificationStats stats, int reason,
+ in long dispatchToken);
void onNotificationRemovedFull(in StatusBarNotification sbn,
- in NotificationRankingUpdate update, in NotificationStats stats, int reason);
- void onNotificationRankingUpdate(in NotificationRankingUpdate update);
- void onListenerHintsChanged(int hints);
- void onInterruptionFilterChanged(int interruptionFilter);
+ in NotificationRankingUpdate update, in NotificationStats stats, int reason,
+ in long dispatchToken);
+ void onNotificationRankingUpdate(in NotificationRankingUpdate update, in long dispatchToken);
+ void onListenerHintsChanged(int hints, in long dispatchToken);
+ void onInterruptionFilterChanged(int interruptionFilter, in long dispatchToken);
// companion device managers and assistants only
- void onNotificationChannelModification(String pkgName, in UserHandle user, in NotificationChannel channel, int modificationType);
- void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType);
+ void onNotificationChannelModification(String pkgName, in UserHandle user, in NotificationChannel channel, int modificationType, in long dispatchToken);
+ void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType, in long dispatchToken);
// assistants only
void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update);
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index f230065..643b250 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -23,7 +23,6 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
@@ -433,6 +432,9 @@
@GuardedBy("mLock")
private RankingMap mRankingMap;
+ @GuardedBy("mLock")
+ private IDispatchCompletionListener mCompletionListener;
+
/**
* @hide
*/
@@ -466,6 +468,16 @@
}
/**
+ * Returns the handler for tests.
+ * @hide
+ */
+ @VisibleForTesting
+ @Nullable
+ public final Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
* Implement this method to learn about new notifications as they are posted by apps.
*
* @param sbn A data structure encapsulating the original {@link android.app.Notification}
@@ -1315,6 +1327,9 @@
@Override
public void onDestroy() {
+ synchronized (mLock) {
+ mCompletionListener = null;
+ }
onListenerDisconnected();
super.onDestroy();
}
@@ -1480,24 +1495,26 @@
protected class NotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
- NotificationRankingUpdate update) {
+ NotificationRankingUpdate update, long dispatchToken) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
+ notifyDispatchCompletion(dispatchToken);
return;
}
if (sbn == null) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
+ notifyDispatchCompletion(dispatchToken);
return;
}
- onNotificationPostedFull(sbn, update);
+ onNotificationPostedFull(sbn, update, dispatchToken);
}
@Override
public void onNotificationPostedFull(StatusBarNotification sbn,
- NotificationRankingUpdate update) {
+ NotificationRankingUpdate update, long dispatchToken) {
try {
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(sbn.getNotification());
@@ -1510,41 +1527,49 @@
sbn = null;
}
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
+
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mLock) {
applyUpdateLocked(update);
if (sbn != null) {
- SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
+
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
} else {
// still pass along the ranking map, it may contain other information
+ args.arg1 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
- mRankingMap).sendToTarget();
+ args).sendToTarget();
}
}
}
@Override
public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
- NotificationRankingUpdate update, NotificationStats stats, int reason) {
+ NotificationRankingUpdate update, NotificationStats stats, int reason,
+ long dispatchToken) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
+ notifyDispatchCompletion(dispatchToken);
return;
}
- onNotificationRemovedFull(sbn, update, stats, reason);
+ onNotificationRemovedFull(sbn, update, stats, reason, dispatchToken);
}
@Override
public void onNotificationRemovedFull(StatusBarNotification sbn,
- NotificationRankingUpdate update, NotificationStats stats, int reason) {
+ NotificationRankingUpdate update, NotificationStats stats, int reason,
+ long dispatchToken) {
if (sbn == null) {
Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification");
+ notifyDispatchCompletion(dispatchToken);
return;
}
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
@@ -1555,6 +1580,7 @@
args.arg2 = mRankingMap;
args.arg3 = reason;
args.arg4 = stats;
+ args.argl1 = dispatchToken;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
args).sendToTarget();
}
@@ -1562,37 +1588,53 @@
}
@Override
- public void onListenerConnected(NotificationRankingUpdate update) {
+ public void onListenerConnected(NotificationRankingUpdate update,
+ IDispatchCompletionListener completionListener, long dispatchToken) {
+ if (Flags.reportNlsStartAndEnd() && completionListener == null) {
+ Log.e(TAG, "No completion listener supplied for this service!");
+ }
+
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mLock) {
applyUpdateLocked(update);
+ mCompletionListener = completionListener;
}
isConnected = true;
- mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
+ mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED, args).sendToTarget();
}
@Override
- public void onNotificationRankingUpdate(NotificationRankingUpdate update)
- throws RemoteException {
+ public void onNotificationRankingUpdate(NotificationRankingUpdate update,
+ long dispatchToken) throws RemoteException {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mLock) {
applyUpdateLocked(update);
+ args.arg1 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
- mRankingMap).sendToTarget();
+ args).sendToTarget();
}
}
@Override
- public void onListenerHintsChanged(int hints) throws RemoteException {
+ public void onListenerHintsChanged(int hints, long dispatchToken) throws RemoteException {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
- hints, 0).sendToTarget();
+ hints, 0, args).sendToTarget();
}
@Override
- public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
+ public void onInterruptionFilterChanged(int interruptionFilter,
+ long dispatchToken) throws RemoteException {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
- interruptionFilter, 0).sendToTarget();
+ interruptionFilter, 0, args).sendToTarget();
}
@Override
@@ -1681,12 +1723,13 @@
@Override
public void onNotificationChannelModification(String pkgName, UserHandle user,
NotificationChannel channel,
- @ChannelOrGroupModificationTypes int modificationType) {
+ @ChannelOrGroupModificationTypes int modificationType, long dispatchToken) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = pkgName;
args.arg2 = user;
args.arg3 = channel;
args.arg4 = modificationType;
+ args.argl1 = dispatchToken;
mHandler.obtainMessage(
MyHandler.MSG_ON_NOTIFICATION_CHANNEL_MODIFIED, args).sendToTarget();
}
@@ -1694,20 +1737,25 @@
@Override
public void onNotificationChannelGroupModification(String pkgName, UserHandle user,
NotificationChannelGroup group,
- @ChannelOrGroupModificationTypes int modificationType) {
+ @ChannelOrGroupModificationTypes int modificationType, long dispatchToken) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = pkgName;
args.arg2 = user;
args.arg3 = group;
args.arg4 = modificationType;
+ args.argl1 = dispatchToken;
mHandler.obtainMessage(
MyHandler.MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED, args).sendToTarget();
}
@Override
- public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
+ public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons,
+ long dispatchToken) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argl1 = dispatchToken;
+ args.argi1 = hideSilentStatusIcons ? 1 : 0;
mHandler.obtainMessage(MyHandler.MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED,
- hideSilentStatusIcons).sendToTarget();
+ args).sendToTarget();
}
@Override
@@ -2465,6 +2513,20 @@
}
}
+ private void notifyDispatchCompletion(long token) {
+ synchronized (mLock) {
+ if (!Flags.reportNlsStartAndEnd() || mCompletionListener == null) {
+ // System listeners are not bound so we don't supply them a mCompletionListener.
+ return;
+ }
+ try {
+ mCompletionListener.notifyDispatchComplete(token);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot send dispatch completion to the system", e);
+ }
+ }
+ }
+
private final class MyHandler extends Handler {
public static final int MSG_ON_NOTIFICATION_POSTED = 1;
public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
@@ -2490,8 +2552,9 @@
SomeArgs args = (SomeArgs) msg.obj;
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
RankingMap rankingMap = (RankingMap) args.arg2;
- args.recycle();
onNotificationPosted(sbn, rankingMap);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_NOTIFICATION_REMOVED: {
@@ -2500,27 +2563,40 @@
RankingMap rankingMap = (RankingMap) args.arg2;
int reason = (int) args.arg3;
NotificationStats stats = (NotificationStats) args.arg4;
- args.recycle();
onNotificationRemoved(sbn, rankingMap, stats, reason);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_LISTENER_CONNECTED: {
+ SomeArgs args = (SomeArgs) msg.obj;
onListenerConnected();
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
- RankingMap rankingMap = (RankingMap) msg.obj;
+ SomeArgs args = (SomeArgs) msg.obj;
+ RankingMap rankingMap = (RankingMap) args.arg1;
onNotificationRankingUpdate(rankingMap);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_LISTENER_HINTS_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
final int hints = msg.arg1;
onListenerHintsChanged(hints);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
final int interruptionFilter = msg.arg1;
onInterruptionFilterChanged(interruptionFilter);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_NOTIFICATION_CHANNEL_MODIFIED: {
@@ -2529,8 +2605,9 @@
UserHandle user= (UserHandle) args.arg2;
NotificationChannel channel = (NotificationChannel) args.arg3;
int modificationType = (int) args.arg4;
- args.recycle();
onNotificationChannelModified(pkgName, user, channel, modificationType);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED: {
@@ -2539,12 +2616,16 @@
UserHandle user = (UserHandle) args.arg2;
NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
int modificationType = (int) args.arg4;
- args.recycle();
onNotificationChannelGroupModified(pkgName, user, group, modificationType);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
case MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED: {
- onSilentStatusBarIconsVisibilityChanged((Boolean) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ onSilentStatusBarIconsVisibilityChanged(args.argi1 == 1);
+ notifyDispatchCompletion(args.argl1);
+ args.recycle();
} break;
}
}
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index ee2585d..3ee67af 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -75,6 +75,13 @@
}
flag {
+ name: "report_nls_start_and_end"
+ namespace: "backstage_power"
+ description: "Reports NLS calls start and end to ActivityManager for process capability adjustments"
+ bug: "398072400"
+}
+
+flag {
name: "notification_get_original_importance"
is_exported: true
namespace: "systemui"
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 78185c0..cfd50c3 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -23,6 +23,7 @@
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Flags.reportNlsStartAndEnd;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
import static com.android.server.notification.Flags.FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER;
@@ -33,11 +34,13 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.IBinderSession;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Context.BindServiceFlags;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
@@ -359,19 +362,12 @@
return userSet != null && userSet.remove(approvedValue);
}
- protected int getBindFlags() {
+ protected long getBindFlags() {
return BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT;
}
protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
- private ManagedServiceInfo newServiceInfo(IInterface service,
- ComponentName component, int userId, boolean isSystem, ServiceConnection connection,
- int targetSdkVersion, int uid) {
- return new ManagedServiceInfo(service, component, userId, isSystem, connection,
- targetSdkVersion, uid);
- }
-
public void onBootPhaseAppsCanStart() {}
public void dump(PrintWriter pw, DumpFilter filter) {
@@ -1865,7 +1861,8 @@
IInterface mService;
@Override
- public void onServiceConnected(ComponentName name, IBinder binder) {
+ public void onServiceConnected(ComponentName name, IBinder binder,
+ IBinderSession binderSession) {
Slog.v(TAG, userid + " " + getCaption() + " service connected: " + name);
boolean added = false;
ManagedServiceInfo info = null;
@@ -1873,8 +1870,13 @@
mServicesRebinding.remove(servicesBindingTag);
try {
mService = asInterface(binder);
- info = newServiceInfo(mService, name,
- userid, isSystem, this, targetSdkVersion, uid);
+ if (reportNlsStartAndEnd()) {
+ info = new ManagedServiceInfo(mService, name, userid, isSystem,
+ this, targetSdkVersion, uid, binderSession);
+ } else {
+ info = new ManagedServiceInfo(mService, name, userid, isSystem,
+ this, targetSdkVersion, uid);
+ }
binder.linkToDeath(info, 0);
added = mServices.add(info);
} catch (RemoteException e) {
@@ -1887,6 +1889,14 @@
}
@Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Slog.wtfStack(TAG,
+ "onServiceConnected(ComponentName, IBinder) called even when "
+ + "onServiceConnected(ComponentName, IBinder, IBinderSession)"
+ + " was overridden");
+ }
+
+ @Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, userid + " " + getCaption() + " connection lost: " + name);
}
@@ -1916,7 +1926,7 @@
};
if (!mContext.bindServiceAsUser(intent,
serviceConnection,
- getBindFlags(),
+ BindServiceFlags.of(getBindFlags()),
new UserHandle(userid))) {
mServicesBound.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent
@@ -2002,7 +2012,7 @@
private ManagedServiceInfo registerServiceImpl(final IInterface service,
final ComponentName component, final int userid, int targetSdk, int uid) {
- ManagedServiceInfo info = newServiceInfo(service, component, userid,
+ ManagedServiceInfo info = new ManagedServiceInfo(service, component, userid,
true /*isSystem*/, null /*connection*/, targetSdk, uid);
return registerServiceImpl(info);
}
@@ -2105,10 +2115,17 @@
public int uid;
@FlaggedApi(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public boolean isVisibleBackgroundUserService;
+ public final IBinderSession mBinderSession;
public ManagedServiceInfo(IInterface service, ComponentName component,
int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion,
int uid) {
+ this(service, component, userid, isSystem, connection, targetSdkVersion, uid, null);
+ }
+
+ public ManagedServiceInfo(IInterface service, ComponentName component,
+ int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion,
+ int uid, IBinderSession binderSession) {
this.service = service;
this.component = component;
this.userid = userid;
@@ -2121,6 +2138,7 @@
.getService(UserManagerInternal.class).isVisibleBackgroundFullUser(userid);
}
mKey = Pair.create(component, userid);
+ mBinderSession = binderSession;
}
public boolean isGuest(ManagedServices host) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1633bdb..9f40c78 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -96,6 +96,7 @@
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_NOT_PERCEPTIBLE;
+import static android.content.Context.BIND_SIMULATE_ALLOW_FREEZE;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
@@ -129,6 +130,7 @@
import static android.service.notification.Flags.notificationRegroupOnClassification;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
+import static android.service.notification.Flags.reportNlsStartAndEnd;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -216,6 +218,7 @@
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.IActivityManager;
+import android.app.IBinderSession;
import android.app.ICallNotificationEventCallback;
import android.app.INotificationManager;
import android.app.ITransientNotification;
@@ -313,6 +316,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.IConditionProvider;
+import android.service.notification.IDispatchCompletionListener;
import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
import android.service.notification.ListenersDisablingEffectsProto;
@@ -13409,6 +13413,8 @@
}
public class NotificationListeners extends ManagedServices {
+ private static final Duration NLS_COMPLETION_GRACE_PERIOD = Duration.ofSeconds(10);
+
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
static final String TAG_REQUESTED_LISTENERS = "request_listeners";
static final String TAG_REQUESTED_LISTENER = "listener";
@@ -13421,6 +13427,21 @@
static final String XML_SEPARATOR = ",";
static final String FLAG_SEPARATOR = "\\|";
+ static final String BINDER_TAG_ON_LISTENER_CONNECTED = "onListenerConnected";
+ static final String BINDER_TAG_ON_NOTIFICATION_POSTED = "onNotificationPostedFull";
+ static final String BINDER_TAG_ON_STATUS_BAR_ICONS_BEHAVIOR_CHANGED =
+ "onStatusBarIconsBehaviorChanged";
+ static final String BINDER_TAG_ON_NOTIFICATION_REMOVED = "onNotificationRemovedFull";
+ static final String BINDER_TAG_ON_NOTIFICATION_RANKING_UPDATE =
+ "onNotificationRankingUpdate";
+ static final String BINDER_TAG_ON_LISTENER_HINTS_CHANGED = "onListenerHintsChanged";
+ static final String BINDER_TAG_ON_INTERRUPTION_FILTER_CHANGED =
+ "onInterruptionFilterChanged";
+ static final String BINDER_TAG_ON_NOTIFICATION_CHANNEL_MODIFICATION =
+ "onNotificationChannelModification";
+ static final String BINDER_TAG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFICATION =
+ "onNotificationChannelGroupModification";
+
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
@GuardedBy("mTrustedListenerUids")
@@ -13497,13 +13518,17 @@
}
@Override
- protected int getBindFlags() {
+ protected long getBindFlags() {
+ long freezeFlags = 0;
+ if (reportNlsStartAndEnd()) {
+ freezeFlags = BIND_SIMULATE_ALLOW_FREEZE;
+ }
// Most of the same flags as the base, but also add BIND_NOT_PERCEPTIBLE
// because too many 3P apps could be kept in memory as notification listeners and
// cause extreme memory pressure.
// TODO: Change the binding lifecycle of NotificationListeners to avoid this situation.
return BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE
- | BIND_NOT_PERCEPTIBLE | BIND_ALLOW_WHITELIST_MANAGEMENT;
+ | BIND_NOT_PERCEPTIBLE | BIND_ALLOW_WHITELIST_MANAGEMENT | freezeFlags;
}
@Override
@@ -13529,6 +13554,16 @@
return service instanceof INotificationListener;
}
+ private long reportBinderTransactionStarting(@NonNull IBinderSession session,
+ @NonNull String tag) {
+ try {
+ return session.binderTransactionStarting(tag);
+ } catch (RemoteException re) {
+ Slog.wtf(TAG, "Local call threw a remote exception", re);
+ }
+ return 0;
+ }
+
@Override
public void onServiceAdded(ManagedServiceInfo info) {
if (lifetimeExtensionRefactor()) {
@@ -13559,8 +13594,29 @@
mTrustedListenerUids.add(info.uid);
}
}
+ final IDispatchCompletionListener completionListener;
+ final long token;
+ if ((info.mBinderSession != null && reportNlsStartAndEnd())) {
+ final IBinderSession session = info.mBinderSession;
+ token = reportBinderTransactionStarting(session, BINDER_TAG_ON_LISTENER_CONNECTED);
+ completionListener = new IDispatchCompletionListener.Stub() {
+ @Override
+ public void notifyDispatchComplete(long dispatchToken) {
+ mHandler.postDelayed(() -> {
+ try {
+ session.binderTransactionCompleted(dispatchToken);
+ } catch (RemoteException e) {
+ // Local call
+ }
+ }, NLS_COMPLETION_GRACE_PERIOD.toMillis());
+ }
+ };
+ } else {
+ completionListener = null;
+ token = 0; // Will never be used as there is no completionListener.
+ }
try {
- listener.onListenerConnected(update);
+ listener.onListenerConnected(update, completionListener, token);
} catch (RemoteException e) {
// we tried
}
@@ -13828,10 +13884,12 @@
// send to all currently bounds NASes since notifications from both users will appear in
// the status bar
for (final ManagedServiceInfo info : getServices()) {
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_STATUS_BAR_ICONS_BEHAVIOR_CHANGED);
mHandler.post(() -> {
final INotificationListener listener = (INotificationListener) info.service;
try {
- listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons, token);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener "
+ "(hideSilentStatusIcons): " + info, ex);
@@ -13840,6 +13898,14 @@
}
}
+ private long getDispatchReportingToken(ManagedServiceInfo info, String input) {
+ if (info.mBinderSession != null && reportNlsStartAndEnd()) {
+ return reportBinderTransactionStarting(info.mBinderSession, input);
+ } else {
+ return 0; // Will be unused because dispatch completion is disabled.
+ }
+ }
+
/**
* Asynchronously notify all listeners about a new or updated notification. Note that the
* notification is new or updated from the point of view of the NLS, but might not be
@@ -14373,15 +14439,17 @@
}
}
- private void notifyPosted(final ManagedServiceInfo info,
+ @VisibleForTesting
+ void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
+ final long token = getDispatchReportingToken(info, BINDER_TAG_ON_NOTIFICATION_POSTED);
try {
if (android.app.Flags.noSbnholder()) {
- listener.onNotificationPostedFull(sbn, rankingUpdate);
+ listener.onNotificationPostedFull(sbn, rankingUpdate, token);
} else {
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
- listener.onNotificationPosted(sbnHolder, rankingUpdate);
+ listener.onNotificationPosted(sbnHolder, rankingUpdate, token);
}
} catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
@@ -14390,7 +14458,8 @@
}
}
- private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
+ @VisibleForTesting
+ void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
final INotificationListener listener = (INotificationListener) info.service;
try {
@@ -14404,11 +14473,13 @@
&& reason == REASON_ASSISTANT_CANCEL) {
reason = REASON_LISTENER_CANCEL;
}
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_NOTIFICATION_REMOVED);
if (android.app.Flags.noSbnholder()) {
- listener.onNotificationRemovedFull(sbn, rankingUpdate, stats, reason);
+ listener.onNotificationRemovedFull(sbn, rankingUpdate, stats, reason, token);
} else {
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
- listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+ listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason, token);
}
} catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
@@ -14417,11 +14488,14 @@
}
}
- private void notifyRankingUpdate(ManagedServiceInfo info,
+ @VisibleForTesting
+ void notifyRankingUpdate(ManagedServiceInfo info,
NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_NOTIFICATION_RANKING_UPDATE);
try {
- listener.onNotificationRankingUpdate(rankingUpdate);
+ listener.onNotificationRankingUpdate(rankingUpdate, token);
} catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
} catch (RemoteException ex) {
@@ -14429,20 +14503,26 @@
}
}
- private void notifyListenerHintsChanged(ManagedServiceInfo info, int hints) {
+ @VisibleForTesting
+ void notifyListenerHintsChanged(ManagedServiceInfo info, int hints) {
final INotificationListener listener = (INotificationListener) info.service;
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_LISTENER_HINTS_CHANGED);
try {
- listener.onListenerHintsChanged(hints);
+ listener.onListenerHintsChanged(hints, token);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (listener hints): " + info, ex);
}
}
- private void notifyInterruptionFilterChanged(ManagedServiceInfo info,
+ @VisibleForTesting
+ void notifyInterruptionFilterChanged(ManagedServiceInfo info,
int interruptionFilter) {
final INotificationListener listener = (INotificationListener) info.service;
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_INTERRUPTION_FILTER_CHANGED);
try {
- listener.onInterruptionFilterChanged(interruptionFilter);
+ listener.onInterruptionFilterChanged(interruptionFilter, token);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (interruption filter): " + info, ex);
}
@@ -14452,19 +14532,26 @@
final String pkg, final UserHandle user, final NotificationChannel channel,
final int modificationType) {
final INotificationListener listener = (INotificationListener) info.service;
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_NOTIFICATION_CHANNEL_MODIFICATION);
try {
- listener.onNotificationChannelModification(pkg, user, channel, modificationType);
+ listener.onNotificationChannelModification(pkg, user, channel, modificationType,
+ token);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (channel changed): " + info, ex);
}
}
- private void notifyNotificationChannelGroupChanged(ManagedServiceInfo info,
+ @VisibleForTesting
+ void notifyNotificationChannelGroupChanged(ManagedServiceInfo info,
final String pkg, final UserHandle user, final NotificationChannelGroup group,
final int modificationType) {
final INotificationListener listener = (INotificationListener) info.getService();
+ final long token = getDispatchReportingToken(info,
+ BINDER_TAG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFICATION);
try {
- listener.onNotificationChannelGroupModification(pkg, user, group, modificationType);
+ listener.onNotificationChannelGroupModification(pkg, user, group, modificationType,
+ token);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (channel group changed): " + info, ex);
}
diff --git a/services/tests/uiservicestests/src/android/service/notification/NotificationListenerWrapperTest.java b/services/tests/uiservicestests/src/android/service/notification/NotificationListenerWrapperTest.java
new file mode 100644
index 0000000..1a41587
--- /dev/null
+++ b/services/tests/uiservicestests/src/android/service/notification/NotificationListenerWrapperTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2025 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 android.service.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class NotificationListenerWrapperTest {
+ private static final String TAG = NotificationListenerWrapperTest.class.getSimpleName();
+ private static final String TEST_PACKAGE = "test-package";
+ private static final Random sRandom = new Random(547);
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private INotificationListener mWrapper;
+ private Handler mHandler;
+ private volatile long mLastCompletedToken = -1;
+
+ private final IDispatchCompletionListener mCompletionListener =
+ new IDispatchCompletionListener() {
+ @Override
+ public void notifyDispatchComplete(long l) {
+ Log.d(TAG, "Dispatch completed for token " + l);
+ mLastCompletedToken = l;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // No IPCs here.
+ return null;
+ }
+ };
+
+ private static final class TestService extends NotificationListenerService {
+ void initialize(Context context) {
+ attachBaseContext(context);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ final TestService service = new TestService();
+ service.initialize(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ mWrapper = INotificationListener.Stub.asInterface(service.onBind(mock(Intent.class)));
+ mHandler = service.getHandler();
+ }
+
+ void waitUntilHandlerIdle() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(latch::countDown);
+ assertWithMessage("mHandler inside NotificationListenerService stuck for too long").that(
+ latch.await(30, TimeUnit.SECONDS)).isTrue();
+ }
+
+ private static int generateToken() {
+ return sRandom.nextInt(1000);
+ }
+
+ void connectAndVerifyListener() throws Exception {
+ final long token = generateToken();
+ mWrapper.onListenerConnected(mock(NotificationRankingUpdate.class), mCompletionListener,
+ token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onListenerConnectedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationPostedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ final IStatusBarNotificationHolder mockSbnHolder = mock(IStatusBarNotificationHolder.class);
+ when(mockSbnHolder.get()).thenReturn(mock(StatusBarNotification.class));
+ mWrapper.onNotificationPosted(mockSbnHolder, mock(NotificationRankingUpdate.class), token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationPostedFullCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onNotificationPostedFull(mock(StatusBarNotification.class),
+ mock(NotificationRankingUpdate.class), token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onStatusBarIconsBehaviorChangedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onStatusBarIconsBehaviorChanged(false, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationRemovedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ final IStatusBarNotificationHolder mockSbnHolder = mock(IStatusBarNotificationHolder.class);
+ when(mockSbnHolder.get()).thenReturn(mock(StatusBarNotification.class));
+ mWrapper.onNotificationRemoved(mockSbnHolder, mock(NotificationRankingUpdate.class),
+ mock(NotificationStats.class), 0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationRemovedFullCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onNotificationRemovedFull(mock(StatusBarNotification.class),
+ mock(NotificationRankingUpdate.class), mock(NotificationStats.class), 0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationRankingUpdateCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onNotificationRankingUpdate(mock(NotificationRankingUpdate.class), token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onListenerHintsChangedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onListenerHintsChanged(0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onInterruptionFilterChangedCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onInterruptionFilterChanged(0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationChannelModificationCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onNotificationChannelModification(TEST_PACKAGE, mock(UserHandle.class), mock(
+ NotificationChannel.class), 0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void onNotificationChannelGroupModificationCallsDispatchCompletion() throws Exception {
+ connectAndVerifyListener();
+ final long token = generateToken();
+ mWrapper.onNotificationChannelGroupModification(TEST_PACKAGE, mock(UserHandle.class),
+ mock(NotificationChannelGroup.class), 0, token);
+ waitUntilHandlerIdle();
+ assertThat(mLastCompletedToken).isEqualTo(token);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index a1b853c..196d934 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -53,6 +53,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.IBinderSession;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -113,6 +114,7 @@
import java.util.concurrent.CountDownLatch;
public class ManagedServicesTest extends UiServiceTestCase {
+ private static final IBinderSession NULL_BINDER_SESSION = null;
@Rule
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -968,10 +970,10 @@
APPROVAL_BY_PACKAGE);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -999,10 +1001,10 @@
APPROVAL_BY_PACKAGE);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1030,10 +1032,10 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1061,10 +1063,10 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1092,10 +1094,10 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1122,10 +1124,10 @@
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1292,7 +1294,7 @@
@DisableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppNoPermissionNoRebind() throws Exception {
Context context = spy(getContext());
- doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), any(), any());
ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
mIpm,
@@ -1345,7 +1347,7 @@
@EnableFlags(FLAG_MANAGED_SERVICES_CONCURRENT_MULTIUSER)
public void testUpgradeAppNoPermissionNoRebind_concurrent_multiUser() throws Exception {
Context context = spy(getContext());
- doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), any(), any());
ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
mIpm,
@@ -1913,7 +1915,7 @@
ComponentName cn = ComponentName.unflattenFromString("a/a");
ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
- when(context.bindServiceAsUser(any(), captor.capture(), anyInt(), any()))
+ when(context.bindServiceAsUser(any(), captor.capture(), any(), any()))
.thenAnswer(invocation -> {
captor.getValue().onNullBinding(cn);
return true;
@@ -1940,10 +1942,10 @@
ComponentName cn = ComponentName.unflattenFromString("a/a");
service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1968,10 +1970,10 @@
ComponentName cn = ComponentName.unflattenFromString("a/a");
service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -1998,10 +2000,10 @@
ComponentName cn = ComponentName.unflattenFromString("a/a");
service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -2029,10 +2031,10 @@
addExpectedServices(service, Arrays.asList("a"), mZero.id);
addExpectedServices(service, Arrays.asList("a"), mTen.id);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn, mock(IBinder.class));
+ sc.onServiceConnected(cn, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
service.addApprovedList("a/a", 0, true);
@@ -2277,10 +2279,10 @@
final ComponentName cn_allowed = ComponentName.unflattenFromString("anotherPackage/C1");
final ComponentName cn_disallowed = ComponentName.unflattenFromString("package/C1");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn_allowed, mock(IBinder.class));
+ sc.onServiceConnected(cn_allowed, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -2326,10 +2328,10 @@
service = spy(service);
when(service.isBoundOrRebinding(cn_disallowed, 0)).thenReturn(true);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn_disallowed, mock(IBinder.class));
+ sc.onServiceConnected(cn_disallowed, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
@@ -2365,10 +2367,10 @@
APPROVAL_BY_COMPONENT);
final ComponentName cn_disallowed = ComponentName.unflattenFromString("package/C1");
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ when(context.bindServiceAsUser(any(), any(), any(), any())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
ServiceConnection sc = (ServiceConnection) args[1];
- sc.onServiceConnected(cn_disallowed, mock(IBinder.class));
+ sc.onServiceConnected(cn_disallowed, mock(IBinder.class), NULL_BINDER_SESSION);
return true;
});
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index bf33333..962d8af 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS;
+import static android.content.Context.BIND_SIMULATE_ALLOW_FREEZE;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.permission.PermissionManager.PERMISSION_GRANTED;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
@@ -23,7 +24,17 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_INTERRUPTION_FILTER_CHANGED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_LISTENER_CONNECTED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_LISTENER_HINTS_CHANGED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFICATION;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_NOTIFICATION_CHANNEL_MODIFICATION;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_NOTIFICATION_POSTED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_NOTIFICATION_RANKING_UPDATE;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_NOTIFICATION_REMOVED;
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.BINDER_TAG_ON_STATUS_BAR_ICONS_BEHAVIOR_CHANGED;
import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
import static com.google.common.truth.Truth.assertThat;
@@ -34,6 +45,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
@@ -51,6 +63,7 @@
import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
+import android.app.IBinderSession;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -64,11 +77,14 @@
import android.content.pm.ServiceInfo;
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.Flags;
import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
import android.service.notification.NotificationListenerFilter;
@@ -108,6 +124,9 @@
@SuppressLint("GuardedBy")
public class NotificationListenersTest extends UiServiceTestCase {
+ private static final int TEST_UID = 548931;
+ private static final int TEST_USER_ID = UserHandle.getUserId(TEST_UID);
+ private static final int TARGET_SDK_VERSION = Build.VERSION.SDK_INT;
@Rule
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -421,6 +440,143 @@
}
@Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testBindFlagsIncludesSimulateAllowFreeze() {
+ assertThat(mListeners.getBindFlags() & BIND_SIMULATE_ALLOW_FREEZE).isEqualTo(
+ BIND_SIMULATE_ALLOW_FREEZE);
+ }
+
+ private ManagedServices.ManagedServiceInfo getNewManagedServiceInfo(
+ IBinderSession iBinderSession) {
+ return mListeners.new ManagedServiceInfo(mock(INotificationListener.class), mCn1,
+ TEST_USER_ID, false, null, TARGET_SDK_VERSION, TEST_UID, iBinderSession);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testOnServiceAddedCallsBinderTransactionStarting() throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ mListeners.onServiceAdded(info);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_LISTENER_CONNECTED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyPostedCallsBinderTransactionStarting() throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final NotificationRankingUpdate nru = mock(NotificationRankingUpdate.class);
+ mListeners.notifyPosted(info, sbn, nru);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_NOTIFICATION_POSTED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testOnStatusBarIconsBehaviorChangedCallsBinderTransactionStarting()
+ throws RemoteException {
+ final IBinderSession iBinderSession1 = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info1 = getNewManagedServiceInfo(iBinderSession1);
+
+ final IBinderSession iBinderSession2 = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info2 = getNewManagedServiceInfo(iBinderSession2);
+
+ doReturn(ImmutableList.of(info1, info2)).when(mListeners).getServices();
+
+ mNm.setHandler(mock(NotificationManagerService.WorkerHandler.class));
+ when(mNm.mHandler.post(any(Runnable.class))).thenAnswer(inv -> {
+ final Runnable r = inv.getArgument(0);
+ r.run();
+ return true;
+ });
+ mListeners.onStatusBarIconsBehaviorChanged(false);
+
+ verify(iBinderSession1).binderTransactionStarting(
+ BINDER_TAG_ON_STATUS_BAR_ICONS_BEHAVIOR_CHANGED);
+ verify(iBinderSession2).binderTransactionStarting(
+ BINDER_TAG_ON_STATUS_BAR_ICONS_BEHAVIOR_CHANGED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyRemovedCallsBinderTransactionStarting() throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final NotificationRankingUpdate nru = mock(NotificationRankingUpdate.class);
+ final NotificationStats stats = mock(NotificationStats.class);
+ final int testReason = 0;
+ mListeners.notifyRemoved(info, sbn, nru, stats, testReason);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_NOTIFICATION_REMOVED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyRankingUpdateCallsBinderTransactionStarting() throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final NotificationRankingUpdate nru = mock(NotificationRankingUpdate.class);
+ mListeners.notifyRankingUpdate(info, nru);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_NOTIFICATION_RANKING_UPDATE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyListenerHintsChangedCallsBinderTransactionStarting()
+ throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final int hints = 43; // Unused value.
+ mListeners.notifyListenerHintsChanged(info, hints);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_LISTENER_HINTS_CHANGED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyInterruptionFilterChangedCallsBinderTransactionStarting()
+ throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final int filter = 43; // Unused value.
+ mListeners.notifyInterruptionFilterChanged(info, filter);
+ verify(iBinderSession).binderTransactionStarting(BINDER_TAG_ON_INTERRUPTION_FILTER_CHANGED);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyNotificationChannelChangedCallsBinderTransactionStarting()
+ throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final int testUid = 330023;
+ final String testPkg = "test-package";
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+
+ final NotificationChannel channel = mock(NotificationChannel.class);
+ mListeners.notifyNotificationChannelChanged(info, testPkg,
+ UserHandle.getUserHandleForUid(testUid), channel,
+ NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ verify(iBinderSession).binderTransactionStarting(
+ BINDER_TAG_ON_NOTIFICATION_CHANNEL_MODIFICATION);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REPORT_NLS_START_AND_END)
+ public void testNotifyNotificationChannelGroupChangedCallsBinderTransactionStarting()
+ throws RemoteException {
+ final IBinderSession iBinderSession = mock(IBinderSession.class);
+ final int testUid = 330023;
+ final String testPkg = "test-package";
+ final ManagedServices.ManagedServiceInfo info = getNewManagedServiceInfo(iBinderSession);
+ final NotificationChannelGroup channelGroup = mock(NotificationChannelGroup.class);
+ mListeners.notifyNotificationChannelGroupChanged(info, testPkg,
+ UserHandle.getUserHandleForUid(testUid), channelGroup,
+ NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ verify(iBinderSession).binderTransactionStarting(
+ BINDER_TAG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFICATION);
+ }
+
+ @Test
public void testOnPackageChanged() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
VersionedPackage a1 = new VersionedPackage("pkg1", 243);
@@ -678,7 +834,8 @@
Thread.sleep(500);
verify(((INotificationListener) i1.getService()), times(1))
- .onNotificationChannelGroupModification(anyString(), any(), any(), anyInt());
+ .onNotificationChannelGroupModification(anyString(), any(), any(), anyInt(),
+ anyLong());
}
@Test
@@ -885,9 +1042,7 @@
otherInfo2);
when(mListeners.getServices()).thenReturn(services);
- FieldSetter.setField(mNm,
- NotificationManagerService.class.getDeclaredField("mHandler"),
- mock(NotificationManagerService.WorkerHandler.class));
+ mNm.setHandler(mock(NotificationManagerService.WorkerHandler.class));
doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
.makeRankingUpdateLocked(sysuiInfo);
@@ -903,7 +1058,7 @@
// Post notification change to the service listeners.
mListeners.notifyPostedLocked(toPost, old);
- // Verify that the post occcurs with the updated notification value.
+ // Verify that the post occurs with the updated notification value.
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(mNm.mHandler, times(1)).post(runnableCaptor.capture());
runnableCaptor.getValue().run();
@@ -911,20 +1066,22 @@
if (android.app.Flags.noSbnholder()) {
ArgumentCaptor<StatusBarNotification> sbnCaptor =
ArgumentCaptor.forClass(StatusBarNotification.class);
- verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue();
} else {
ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
- verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue().get();
}
assertThat(sbnResult.getNotification()
.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
.isEqualTo("new title");
- verify(otherListener1, never()).onNotificationPosted(any(), any());
- verify(otherListener2, never()).onNotificationPosted(any(), any());
+ verify(otherListener1, never()).onNotificationPosted(any(), any(), anyLong());
+ verify(otherListener2, never()).onNotificationPosted(any(), any(), anyLong());
}
@Test
@@ -978,9 +1135,7 @@
otherInfo2);
when(mListeners.getServices()).thenReturn(services);
- FieldSetter.setField(mNm,
- NotificationManagerService.class.getDeclaredField("mHandler"),
- mock(NotificationManagerService.WorkerHandler.class));
+ mNm.setHandler(mock(NotificationManagerService.WorkerHandler.class));
doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
.makeRankingUpdateLocked(sysuiInfo);
@@ -1010,12 +1165,14 @@
if (android.app.Flags.noSbnholder()) {
ArgumentCaptor<StatusBarNotification> sbnCaptor =
ArgumentCaptor.forClass(StatusBarNotification.class);
- verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue();
} else {
ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
- verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue().get();
}
assertThat(sbnResult.getNotification()
@@ -1023,11 +1180,11 @@
.isEqualTo("new title");
if (android.app.Flags.noSbnholder()) {
- verify(otherListener1, times(1)).onNotificationPostedFull(any(), any());
- verify(otherListener2, times(1)).onNotificationPostedFull(any(), any());
+ verify(otherListener1, times(1)).onNotificationPostedFull(any(), any(), anyLong());
+ verify(otherListener2, times(1)).onNotificationPostedFull(any(), any(), anyLong());
} else {
- verify(otherListener1, times(1)).onNotificationPosted(any(), any());
- verify(otherListener2, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any(), anyLong());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any(), anyLong());
}
}
@@ -1078,9 +1235,7 @@
otherInfo2);
when(mListeners.getServices()).thenReturn(services);
- FieldSetter.setField(mNm,
- NotificationManagerService.class.getDeclaredField("mHandler"),
- mock(NotificationManagerService.WorkerHandler.class));
+ mNm.setHandler(mock(NotificationManagerService.WorkerHandler.class));
doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
.makeRankingUpdateLocked(sysuiInfo);
@@ -1108,12 +1263,14 @@
if (android.app.Flags.noSbnholder()) {
ArgumentCaptor<StatusBarNotification> sbnCaptor =
ArgumentCaptor.forClass(StatusBarNotification.class);
- verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPostedFull(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue();
} else {
ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
- verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any(),
+ anyLong());
sbnResult = sbnCaptor.getValue().get();
}
assertThat(sbnResult.getNotification()
@@ -1121,11 +1278,11 @@
.isEqualTo("new title");
if (android.app.Flags.noSbnholder()) {
- verify(otherListener1, times(1)).onNotificationPostedFull(any(), any());
- verify(otherListener2, times(1)).onNotificationPostedFull(any(), any());
+ verify(otherListener1, times(1)).onNotificationPostedFull(any(), any(), anyLong());
+ verify(otherListener2, times(1)).onNotificationPostedFull(any(), any(), anyLong());
} else {
- verify(otherListener1, times(1)).onNotificationPosted(any(), any());
- verify(otherListener2, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any(), anyLong());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any(), anyLong());
}
}
@@ -1177,7 +1334,8 @@
}
return null;
- }).when(l1).onNotificationChannelGroupModification(anyString(), any(), any(), anyInt());
+ }).when(l1).onNotificationChannelGroupModification(anyString(), any(), any(), anyInt(),
+ anyLong());
return i1;
}