summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/current.txt7
-rw-r--r--core/java/android/app/INotificationManager.aidl5
-rw-r--r--core/java/android/app/NotificationManager.java128
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java16
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java161
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java151
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java178
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java161
9 files changed, 694 insertions, 117 deletions
diff --git a/api/current.txt b/api/current.txt
index d3ef74fcde96..d82dcad9f7bc 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -5680,6 +5680,7 @@ package android.app {
public class NotificationManager {
method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule);
method public boolean areNotificationsEnabled();
+ method public boolean canNotifyAsPackage(java.lang.String);
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
@@ -5698,13 +5699,17 @@ package android.app {
method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
+ method public java.lang.String getNotificationDelegate();
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
method public void notify(java.lang.String, int, android.app.Notification);
+ method public void notifyAsPackage(java.lang.String, java.lang.String, int, android.app.Notification);
method public boolean removeAutomaticZenRule(java.lang.String);
+ method public void revokeNotificationDelegate();
method public final void setInterruptionFilter(int);
+ method public void setNotificationDelegate(java.lang.String);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
@@ -39652,10 +39657,12 @@ package android.service.notification {
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
+ method public java.lang.String getOpPkg();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
method public java.lang.String getTag();
+ method public int getUid();
method public android.os.UserHandle getUser();
method public deprecated int getUserId();
method public boolean isClearable();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 3171e3e3b992..4f004d93e3bf 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -165,4 +165,9 @@ interface INotificationManager
void applyRestore(in byte[] payload, int user);
ParceledListSlice getAppActiveNotifications(String callingPkg, int userId);
+
+ void setNotificationDelegate(String callingPkg, String delegate);
+ void revokeNotificationDelegate(String callingPkg);
+ String getNotificationDelegate(String callingPkg);
+ boolean canNotifyAsPackage(String callingPkg, String targetPkg);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 4b25b8b6e1e0..b96b39df9aa0 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -18,6 +18,7 @@ package android.app;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -352,7 +353,7 @@ public class NotificationManager {
}
/**
- * Post a notification to be shown in the status bar. If a notification with
+ * Posts a notification to be shown in the status bar. If a notification with
* the same tag and id has already been posted by your application and has not yet been
* canceled, it will be replaced by the updated information.
*
@@ -376,6 +377,42 @@ public class NotificationManager {
}
/**
+ * Posts a notification as a specified package to be shown in the status bar. If a notification
+ * with the same tag and id has already been posted for that package and has not yet been
+ * canceled, it will be replaced by the updated information.
+ *
+ * All {@link android.service.notification.NotificationListenerService listener services} will
+ * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
+ * provided on this notification or the
+ * {@link NotificationChannel} this notification is posted to using
+ * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
+ * notification is canceled, or you can revoke permissions with
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ *
+ * @param targetPackage The package to post the notification as. The package must have granted
+ * you access to post notifications on their behalf with
+ * {@link #setNotificationDelegate(String)}.
+ * @param tag A string identifier for this notification. May be {@code null}.
+ * @param id An identifier for this notification. The pair (tag, id) must be unique
+ * within your application.
+ * @param notification A {@link Notification} object describing what to
+ * show the user. Must not be null.
+ */
+ public void notifyAsPackage(@NonNull String targetPackage, @NonNull String tag, int id,
+ Notification notification) {
+ INotificationManager service = getService();
+ String sender = mContext.getPackageName();
+
+ try {
+ if (localLOGV) Log.v(TAG, sender + ": notify(" + id + ", " + notification + ")");
+ service.enqueueNotificationWithTag(targetPackage, sender, tag, id,
+ fixNotification(notification), mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -383,6 +420,18 @@ public class NotificationManager {
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+
+ try {
+ if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
+ service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
+ fixNotification(notification), user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Notification fixNotification(Notification notification) {
+ String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
@@ -400,19 +449,12 @@ public class NotificationManager {
+ notification);
}
}
- if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
+
notification.reduceImageSizes(mContext);
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
- final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam,
- mContext);
- try {
- service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
- copy, user.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return Builder.maybeCloneStrippedForDelivery(notification, isLowRam, mContext);
}
private void fixLegacySmallIcon(Notification n, String pkg) {
@@ -474,6 +516,72 @@ public class NotificationManager {
}
/**
+ * Allows a package to post notifications on your behalf using
+ * {@link #notifyAsPackage(String, String, int, Notification)}.
+ *
+ * This can be used to allow persistent processes to post notifications based on messages
+ * received on your behalf from the cloud, without your process having to wake up.
+ *
+ * You can check if you have an allowed delegate with {@link #getNotificationDelegate()} and
+ * revoke your delegate with {@link #revokeNotificationDelegate()}.
+ *
+ * @param delegate Package name of the app which can send notifications on your behalf.
+ */
+ public void setNotificationDelegate(@NonNull String delegate) {
+ INotificationManager service = getService();
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
+ try {
+ service.setNotificationDelegate(pkg, delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Revokes permission for your {@link #setNotificationDelegate(String) notification delegate}
+ * to post notifications on your behalf.
+ */
+ public void revokeNotificationDelegate() {
+ INotificationManager service = getService();
+ String pkg = mContext.getPackageName();
+ try {
+ service.revokeNotificationDelegate(pkg);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the {@link #setNotificationDelegate(String) delegate} that can post notifications on
+ * your behalf, if there currently is one.
+ */
+ public @Nullable String getNotificationDelegate() {
+ INotificationManager service = getService();
+ String pkg = mContext.getPackageName();
+ try {
+ return service.getNotificationDelegate(pkg);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether you are allowed to post notifications on behalf of a given package, with
+ * {@link #notifyAsPackage(String, String, int, Notification)}.
+ *
+ * See {@link #setNotificationDelegate(String)}.
+ */
+ public boolean canNotifyAsPackage(String pkg) {
+ INotificationManager service = getService();
+ try {
+ return service.canNotifyAsPackage(mContext.getPackageName(), pkg);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a group container for {@link NotificationChannel} objects.
*
* This can be used to rename an existing group.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index dd97d524d829..84826e0f6824 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -18,7 +18,7 @@ package android.service.notification;
import android.annotation.UnsupportedAppUsage;
import android.app.Notification;
-import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -261,7 +261,7 @@ public class StatusBarNotification implements Parcelable {
return this.user.getIdentifier();
}
- /** The package of the app that posted the notification. */
+ /** The package that the notification belongs to. */
public String getPackageName() {
return pkg;
}
@@ -277,14 +277,18 @@ public class StatusBarNotification implements Parcelable {
return tag;
}
- /** The notifying app's calling uid. @hide */
- @UnsupportedAppUsage
+ /**
+ * The notifying app's ({@link #getPackageName()}'s) uid.
+ */
public int getUid() {
return uid;
}
- /** The package used for AppOps tracking. @hide */
- @UnsupportedAppUsage
+ /** The package that posted the notification.
+ *<p>
+ * Might be different from {@link #getPackageName()} if the app owning the notification has
+ * a {@link NotificationManager#setNotificationDelegate(String) notification delegate}.
+ */
public String getOpPkg() {
return opPkg;
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 426a0c157aec..fd32b5a76f15 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5296,7 +5296,9 @@ public class AccountManagerService
try {
INotificationManager notificationManager = mInjector.getNotificationManager();
try {
- notificationManager.enqueueNotificationWithTag(packageName, packageName,
+ // The calling uid must match either the package or op package, so use an op
+ // package that matches the cleared calling identity.
+ notificationManager.enqueueNotificationWithTag(packageName, "android",
id.mTag, id.mId, notification, userId);
} catch (RemoteException e) {
/* ignore - local call */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ce71dd2ec9ad..03b7652e32ec 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -35,6 +35,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -203,6 +205,7 @@ import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.pm.PackageManagerService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -470,8 +473,8 @@ public class NotificationManagerService extends SystemService {
// Gather all notification listener components for candidate pkgs.
Set<ComponentName> approvedListeners =
mListeners.queryPackageForServices(whitelisted,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE, userId);
for (ComponentName cn : approvedListeners) {
try {
getBinderService().setNotificationListenerAccessGrantedForUser(cn,
@@ -507,8 +510,8 @@ public class NotificationManagerService extends SystemService {
// only be one
Set<ComponentName> approvedAssistants =
mAssistants.queryPackageForServices(defaultAssistantAccess,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE, userId);
for (ComponentName cn : approvedAssistants) {
try {
getBinderService().setNotificationAssistantAccessGrantedForUser(
@@ -1377,7 +1380,7 @@ public class NotificationManagerService extends SystemService {
NotificationUsageStats usageStats, AtomicFile policyFile,
ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
- IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal) {
+ IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1390,7 +1393,7 @@ public class NotificationManagerService extends SystemService {
mUgmInternal = ugmInternal;
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
- mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mAppOps = appOps;
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = appUsageStats;
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
@@ -1544,7 +1547,8 @@ public class NotificationManagerService extends SystemService {
LocalServices.getService(UsageStatsManagerInternal.class),
LocalServices.getService(DevicePolicyManagerInternal.class),
UriGrantsManager.getService(),
- LocalServices.getService(UriGrantsManagerInternal.class));
+ LocalServices.getService(UriGrantsManagerInternal.class),
+ (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE));
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -2234,6 +2238,60 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void setNotificationDelegate(String callingPkg, String delegate) {
+ checkCallerIsSameApp(callingPkg);
+ final int callingUid = Binder.getCallingUid();
+ UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+ try {
+ ApplicationInfo info =
+ mPackageManager.getApplicationInfo(delegate,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ user.getIdentifier());
+ if (info != null) {
+ mPreferencesHelper.setNotificationDelegate(
+ callingPkg, callingUid, delegate, info.uid);
+ savePolicyFile();
+ }
+ } catch (RemoteException e) {
+ // :(
+ }
+ }
+
+ @Override
+ public void revokeNotificationDelegate(String callingPkg) {
+ checkCallerIsSameApp(callingPkg);
+ mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid());
+ savePolicyFile();
+ }
+
+ @Override
+ public String getNotificationDelegate(String callingPkg) {
+ // callable by Settings also
+ checkCallerIsSystemOrSameApp(callingPkg);
+ return mPreferencesHelper.getNotificationDelegate(callingPkg, Binder.getCallingUid());
+ }
+
+ @Override
+ public boolean canNotifyAsPackage(String callingPkg, String targetPkg) {
+ checkCallerIsSameApp(callingPkg);
+ final int callingUid = Binder.getCallingUid();
+ UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+ try {
+ ApplicationInfo info =
+ mPackageManager.getApplicationInfo(targetPkg,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ user.getIdentifier());
+ if (info != null) {
+ return mPreferencesHelper.isDelegateAllowed(
+ targetPkg, info.uid, callingPkg, callingUid);
+ }
+ } catch (RemoteException e) {
+ // :(
+ }
+ return false;
+ }
+
+ @Override
public void updateNotificationChannelGroupForPackage(String pkg, int uid,
NotificationChannelGroup group) throws RemoteException {
enforceSystemOrSystemUI("Caller not system or systemui");
@@ -4053,20 +4111,21 @@ public class NotificationManagerService extends SystemService {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
- checkCallerIsSystemOrSameApp(pkg);
- checkRestrictedCategories(notification);
-
- final int userId = ActivityManager.handleIncomingUser(callingPid,
- callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
- final UserHandle user = new UserHandle(userId);
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
- // The system can post notifications for any package, let us resolve that.
- final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
+ final int userId = ActivityManager.handleIncomingUser(callingPid,
+ callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
+ final UserHandle user = UserHandle.of(userId);
+
+ // Can throw a SecurityException if the calling uid doesn't have permission to post
+ // as "pkg"
+ final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+
+ checkRestrictedCategories(notification);
// Fix the notification as best we can.
try {
@@ -4193,17 +4252,28 @@ public class NotificationManagerService extends SystemService {
}
}
- private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
- // The system can post notifications on behalf of any package it wants
- if (isCallerSystemOrPhone() && opPackageName != null && !"android".equals(opPackageName)) {
- try {
- return getContext().getPackageManager()
- .getPackageUidAsUser(opPackageName, userId);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
+ @VisibleForTesting
+ int resolveNotificationUid(String callingPkg, String targetPkg,
+ int callingUid, int userId) {
+ // posted from app A on behalf of app A
+ if (isCallerSameApp(targetPkg, callingUid) && TextUtils.equals(callingPkg, targetPkg)) {
+ return callingUid;
+ }
+
+ int targetUid = -1;
+ try {
+ targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
+ } catch (NameNotFoundException e) {
+ /* ignore */
+ }
+ // posted from app A on behalf of app B
+ if (targetUid != -1 && (isCallerAndroid(callingPkg, callingUid)
+ || mPreferencesHelper.isDelegateAllowed(
+ targetPkg, targetUid, callingPkg, callingUid))) {
+ return targetUid;
}
- return callingUid;
+
+ throw new SecurityException("Caller " + callingUid + " cannot post for pkg " + targetPkg);
}
/**
@@ -4222,7 +4292,8 @@ public class NotificationManagerService extends SystemService {
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationLock) {
- if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
+ if (mNotificationsByKey.get(r.sbn.getKey()) == null
+ && isCallerInstantApp(pkg, callingUid)) {
// Ephemeral apps have some special constraints for notifications.
// They are not allowed to create new notifications however they are allowed to
// update notifications created by the system (e.g. a foreground service
@@ -5149,11 +5220,11 @@ public class NotificationManagerService extends SystemService {
try {
Thread.sleep(waitMs);
} catch (InterruptedException e) { }
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(),
effect, "Notification (delayed)", record.getAudioAttributes());
}).start();
} else {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getPackageName(),
effect, "Notification", record.getAudioAttributes());
}
return true;
@@ -6282,6 +6353,11 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSameApp(pkg);
}
+ private boolean isCallerAndroid(String callingPkg, int uid) {
+ return isUidSystemOrPhone(uid) && callingPkg != null
+ && PackageManagerService.PLATFORM_PACKAGE_NAME.equals(callingPkg);
+ }
+
/**
* Check if the notification is of a category type that is restricted to system use only,
* if so throw SecurityException
@@ -6302,13 +6378,13 @@ public class NotificationManagerService extends SystemService {
}
}
- private boolean isCallerInstantApp(String pkg) {
+ private boolean isCallerInstantApp(String pkg, int callingUid) {
// System is always allowed to act for ephemeral apps.
- if (isCallerSystemOrPhone()) {
+ if (isUidSystemOrPhone(callingUid)) {
return false;
}
- mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ mAppOps.checkPackage(callingUid, pkg);
try {
ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0,
@@ -6324,7 +6400,10 @@ public class NotificationManagerService extends SystemService {
}
private void checkCallerIsSameApp(String pkg) {
- final int uid = Binder.getCallingUid();
+ checkCallerIsSameApp(pkg, Binder.getCallingUid());
+ }
+
+ private void checkCallerIsSameApp(String pkg, int uid) {
try {
ApplicationInfo ai = mPackageManager.getApplicationInfo(
pkg, 0, UserHandle.getCallingUserId());
@@ -6340,6 +6419,24 @@ public class NotificationManagerService extends SystemService {
}
}
+ private boolean isCallerSameApp(String pkg) {
+ try {
+ checkCallerIsSameApp(pkg);
+ return true;
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
+ private boolean isCallerSameApp(String pkg, int uid) {
+ try {
+ checkCallerIsSameApp(pkg, uid);
+ return true;
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
private static String callStateToString(int state) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: return "CALL_STATE_IDLE";
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 432d17c821f2..593e7cdf4d66 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -20,6 +20,7 @@ import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -66,12 +67,14 @@ import java.util.concurrent.ConcurrentHashMap;
public class PreferencesHelper implements RankingConfig {
private static final String TAG = "NotificationPrefHelper";
private static final int XML_VERSION = 1;
+ private static final int UNKNOWN_UID = UserHandle.USER_NULL;
@VisibleForTesting
static final String TAG_RANKING = "ranking";
private static final String TAG_PACKAGE = "package";
private static final String TAG_CHANNEL = "channel";
private static final String TAG_GROUP = "channelGroup";
+ private static final String TAG_DELEGATE = "delegate";
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
@@ -82,6 +85,8 @@ public class PreferencesHelper implements RankingConfig {
private static final String ATT_IMPORTANCE = "importance";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
+ private static final String ATT_ENABLED = "enabled";
+ private static final String ATT_USER_ALLOWED = "allowed";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
@@ -147,8 +152,7 @@ public class PreferencesHelper implements RankingConfig {
}
if (type == XmlPullParser.START_TAG) {
if (TAG_PACKAGE.equals(tag)) {
- int uid = XmlUtils.readIntAttribute(parser, ATT_UID,
- PackagePreferences.UNKNOWN_UID);
+ int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
String name = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(name)) {
if (forRestore) {
@@ -217,6 +221,24 @@ public class PreferencesHelper implements RankingConfig {
r.channels.put(id, channel);
}
}
+ // Delegate
+ if (TAG_DELEGATE.equals(tagName)) {
+ int delegateId =
+ XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
+ String delegateName =
+ XmlUtils.readStringAttribute(parser, ATT_NAME);
+ boolean delegateEnabled = XmlUtils.readBooleanAttribute(
+ parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
+ boolean userAllowed = XmlUtils.readBooleanAttribute(
+ parser, ATT_USER_ALLOWED, Delegate.DEFAULT_USER_ALLOWED);
+ Delegate d = null;
+ if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(delegateName)) {
+ d = new Delegate(
+ delegateName, delegateId, delegateEnabled, userAllowed);
+ }
+ r.delegate = d;
+ }
+
}
try {
@@ -248,7 +270,7 @@ public class PreferencesHelper implements RankingConfig {
final String key = packagePreferencesKey(pkg, uid);
synchronized (mPackagePreferencess) {
PackagePreferences
- r = (uid == PackagePreferences.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+ r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
: mPackagePreferencess.get(key);
if (r == null) {
r = new PackagePreferences();
@@ -265,7 +287,7 @@ public class PreferencesHelper implements RankingConfig {
Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
}
- if (r.uid == PackagePreferences.UNKNOWN_UID) {
+ if (r.uid == UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
} else {
mPackagePreferencess.put(key, r);
@@ -357,7 +379,8 @@ public class PreferencesHelper implements RankingConfig {
|| r.showBadge != DEFAULT_SHOW_BADGE
|| r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
|| r.channels.size() > 0
- || r.groups.size() > 0;
+ || r.groups.size() > 0
+ || r.delegate != null;
if (hasNonDefaultSettings) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATT_NAME, r.pkg);
@@ -378,6 +401,21 @@ public class PreferencesHelper implements RankingConfig {
out.attribute(null, ATT_UID, Integer.toString(r.uid));
}
+ if (r.delegate != null) {
+ out.startTag(null, TAG_DELEGATE);
+
+ out.attribute(null, ATT_NAME, r.delegate.mPkg);
+ out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
+ if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+ out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
+ }
+ if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
+ out.attribute(null, ATT_USER_ALLOWED,
+ Boolean.toString(r.delegate.mUserAllowed));
+ }
+ out.endTag(null, TAG_DELEGATE);
+ }
+
for (NotificationChannelGroup group : r.groups.values()) {
group.writeXml(out);
}
@@ -923,16 +961,76 @@ public class PreferencesHelper implements RankingConfig {
* considered for sentiment adjustments (and thus never show a blocking helper).
*/
public void setAppImportanceLocked(String packageName, int uid) {
- PackagePreferences PackagePreferences = getOrCreatePackagePreferences(packageName, uid);
- if ((PackagePreferences.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
+ PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
+ if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
return;
}
- PackagePreferences.lockedAppFields =
- PackagePreferences.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
+ prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
updateConfig();
}
+ /**
+ * Returns the delegate for a given package, if it's allowed by the package and the user.
+ */
+ public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
+ PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+
+ if (prefs == null || prefs.delegate == null) {
+ return null;
+ }
+ if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
+ return null;
+ }
+ return prefs.delegate.mPkg;
+ }
+
+ /**
+ * Used by an app to delegate notification posting privileges to another apps.
+ */
+ public void setNotificationDelegate(String sourcePkg, int sourceUid,
+ String delegatePkg, int delegateUid) {
+ PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
+
+ boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
+ Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
+ prefs.delegate = delegate;
+ updateConfig();
+ }
+
+ /**
+ * Used by an app to turn off its notification delegate.
+ */
+ public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
+ PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+ if (prefs != null && prefs.delegate != null) {
+ prefs.delegate.mEnabled = false;
+ updateConfig();
+ }
+ }
+
+ /**
+ * Toggles whether an app can have a notification delegate on behalf of a user.
+ */
+ public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
+ PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+ if (prefs != null && prefs.delegate != null) {
+ prefs.delegate.mUserAllowed = userAllowed;
+ updateConfig();
+ }
+ }
+
+ /**
+ * Returns whether the given app is allowed on post notifications on behalf of the other given
+ * app.
+ */
+ public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
+ String potentialDelegatePkg, int potentialDelegateUid) {
+ PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
+
+ return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
+ }
+
@VisibleForTesting
void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
if (original.canBypassDnd() != update.canBypassDnd()) {
@@ -994,8 +1092,7 @@ public class PreferencesHelper implements RankingConfig {
pw.print(" AppSettings: ");
pw.print(r.pkg);
pw.print(" (");
- pw.print(r.uid == PackagePreferences.UNKNOWN_UID ? "UNKNOWN_UID"
- : Integer.toString(r.uid));
+ pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
pw.print(')');
if (r.importance != DEFAULT_IMPORTANCE) {
pw.print(" importance=");
@@ -1356,8 +1453,6 @@ public class PreferencesHelper implements RankingConfig {
}
private static class PackagePreferences {
- static int UNKNOWN_UID = UserHandle.USER_NULL;
-
String pkg;
int uid = UNKNOWN_UID;
int importance = DEFAULT_IMPORTANCE;
@@ -1366,7 +1461,37 @@ public class PreferencesHelper implements RankingConfig {
boolean showBadge = DEFAULT_SHOW_BADGE;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+ Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
+
+ public boolean isValidDelegate(String pkg, int uid) {
+ return delegate != null && delegate.isAllowed(pkg, uid);
+ }
+ }
+
+ private static class Delegate {
+ static final boolean DEFAULT_ENABLED = true;
+ static final boolean DEFAULT_USER_ALLOWED = true;
+ String mPkg;
+ int mUid = UNKNOWN_UID;
+ boolean mEnabled = DEFAULT_ENABLED;
+ boolean mUserAllowed = DEFAULT_USER_ALLOWED;
+
+ Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
+ mPkg = pkg;
+ mUid = uid;
+ mEnabled = enabled;
+ mUserAllowed = userAllowed;
+ }
+
+ public boolean isAllowed(String pkg, int uid) {
+ if (pkg == null || uid == UNKNOWN_UID) {
+ return false;
+ }
+ return pkg.equals(mPkg)
+ && uid == mUid
+ && (mUserAllowed && mEnabled);
+ }
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 0ff124e4ce7a..a1b3b988397c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -65,6 +65,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.Application;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -195,6 +197,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
IUriGrantsManager mUgm;
@Mock
UriGrantsManagerInternal mUgmInternal;
+ @Mock
+ AppOpsManager mAppOpsManager;
// Use a Testable subclass so we can simulate calls from the system without failing.
private static class TestableNotificationManagerService extends NotificationManagerService {
@@ -295,7 +299,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mListeners, mAssistants, mConditionProviders,
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
mGroupHelper, mAm, mAppUsageStats,
- mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal);
+ mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
+ mAppOpsManager);
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
@@ -531,7 +536,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.createNotificationChannels(
PKG, new ParceledListSlice(Arrays.asList(channel)));
final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -549,7 +554,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
assertEquals(1, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -578,7 +583,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
// The first time a foreground service notification is shown, we allow the channel
@@ -600,7 +605,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
sbn = generateNotificationRecord(channel).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
// The second time it is shown, we keep the user's preference.
@@ -631,7 +636,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -645,7 +650,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -667,7 +672,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn =
generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
sbn.getNotification().category = category;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
}
waitForIdle();
@@ -691,7 +696,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn =
generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
sbn.getNotification().category = category;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
}
waitForIdle();
@@ -714,7 +719,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().category = category;
try {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
fail("Calls from non system apps should not allow use of restricted categories");
} catch (SecurityException e) {
@@ -746,7 +751,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -756,7 +761,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
@@ -768,10 +773,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
@@ -788,7 +793,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
NotificationRecord r = generateNotificationRecord(null);
final StatusBarNotification sbn = r.sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelNotificationsFromListener(null, null);
waitForIdle();
@@ -801,7 +806,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
@@ -816,7 +821,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord n = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
waitForIdle();
@@ -839,9 +844,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord child = generateNotificationRecord(
mTestNotificationChannel, 2, "group1", false);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
waitForIdle();
@@ -854,7 +859,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
for (int i = 0; i < 10; i++) {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
}
mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
@@ -873,17 +878,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 2, "group1", false);
// fully post parent notification
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
waitForIdle();
// enqueue the child several times
for (int i = 0; i < 10; i++) {
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
}
// make the parent a child, which will cancel the child notification
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
parentAsChild.sbn.getUserId());
waitForIdle();
@@ -895,7 +900,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
@@ -909,7 +914,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
waitForIdle();
@@ -922,7 +927,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications(null, sbn.getUserId());
waitForIdle();
@@ -935,7 +940,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL);
// Null pkg is how we signal a user switch.
mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -950,7 +955,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
@@ -1037,7 +1042,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mInternalService.removeForegroundServiceFlagFromNotification(PKG, sbn.getId(),
sbn.getUserId());
@@ -1052,10 +1057,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags =
Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
waitForIdle();
@@ -1145,21 +1150,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// should not be returned
final NotificationRecord group2 = generateNotificationRecord(
mTestNotificationChannel, 2, "group2", true);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId());
waitForIdle();
// should not be returned
final NotificationRecord nonGroup = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId());
waitForIdle();
// same group, child, should be returned
final NotificationRecord group1Child = generateNotificationRecord(
mTestNotificationChannel, 4, "group1", false);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null, group1Child.sbn.getId(),
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null, group1Child.sbn.getId(),
group1Child.sbn.getNotification(), group1Child.sbn.getUserId());
waitForIdle();
@@ -1216,7 +1221,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
@@ -1333,7 +1338,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
@@ -1348,7 +1353,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
@@ -1879,7 +1884,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord child = generateNotificationRecord(
mTestNotificationChannel, 2, "group", false);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
waitForIdle();
@@ -1892,7 +1897,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord record = generateNotificationRecord(
mTestNotificationChannel, 2, null, false);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId());
waitForIdle();
@@ -1904,7 +1909,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 2, "group", true);
- mBinderService.enqueueNotificationWithTag(PKG, "opPkg", null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
waitForIdle();
@@ -2378,12 +2383,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testBumpFGImportance_noChannelChangePreOApp() throws Exception {
String preOPkg = PKG_N_MR1;
- int preOUid = 145;
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt()))
.thenReturn(legacy);
- when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt())).thenReturn(preOUid);
+ when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt()))
+ .thenReturn(Binder.getCallingUid());
getContext().setMockPackageManager(mPackageManagerClient);
Notification.Builder nb = new Notification.Builder(mContext,
@@ -2393,12 +2398,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setPriority(Notification.PRIORITY_MIN);
- StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
- 0, nb.build(), new UserHandle(preOUid), null, 0);
+ StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag",
+ Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
- mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
- sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
+ sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
+
assertEquals(IMPORTANCE_LOW,
mService.getNotificationRecord(sbn.getKey()).getImportance());
@@ -2408,8 +2414,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setPriority(Notification.PRIORITY_MIN);
- sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
- 0, nb.build(), new UserHandle(preOUid), null, 0);
+ sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", Binder.getCallingUid(),
+ 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -3360,7 +3366,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testMybeRecordInterruptionLocked_doesNotRecordTwice()
+ public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
@@ -3373,4 +3379,78 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mAppUsageStats, times(1)).reportInterruptiveNotification(
anyString(), anyString(), anyInt());
}
+
+ @Test
+ public void testResolveNotificationUid_sameApp() throws Exception {
+ ApplicationInfo info = new ApplicationInfo();
+ info.uid = Binder.getCallingUid();
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+ int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 0);
+
+ assertEquals(info.uid, actualUid);
+ }
+
+ @Test
+ public void testResolveNotificationUid_sameAppWrongPkg() throws Exception {
+ ApplicationInfo info = new ApplicationInfo();
+ info.uid = Binder.getCallingUid();
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+ try {
+ mService.resolveNotificationUid("caller", "other", info.uid, 0);
+ fail("Incorrect pkg didn't throw security exception");
+ } catch (SecurityException e) {
+ // yay
+ }
+ }
+
+ @Test
+ public void testResolveNotificationUid_sameAppWrongUid() throws Exception {
+ ApplicationInfo info = new ApplicationInfo();
+ info.uid = 1356347;
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info);
+
+ try {
+ mService.resolveNotificationUid("caller", "caller", 9, 0);
+ fail("Incorrect uid didn't throw security exception");
+ } catch (SecurityException e) {
+ // yay
+ }
+ }
+
+ @Test
+ public void testResolveNotificationUid_delegateAllowed() throws Exception {
+ int expectedUid = 123;
+
+ when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(expectedUid);
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.isDelegateAllowed(anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(true);
+
+ assertEquals(expectedUid, mService.resolveNotificationUid("caller", "target", 9, 0));
+ }
+
+ @Test
+ public void testResolveNotificationUid_androidAllowed() throws Exception {
+ int expectedUid = 123;
+
+ when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(expectedUid);
+ // no delegate
+
+ assertEquals(expectedUid, mService.resolveNotificationUid("android", "target", 0, 0));
+ }
+
+ @Test
+ public void testResolveNotificationUid_delegateNotAllowed() throws Exception {
+ when(mPackageManagerClient.getPackageUidAsUser("target", 0)).thenReturn(123);
+ // no delegate
+
+ try {
+ mService.resolveNotificationUid("caller", "target", 9, 0);
+ fail("Incorrect uid didn't throw security exception");
+ } catch (SecurityException e) {
+ // yay
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 73adf25cb3ec..750345be1c1d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -123,7 +123,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- UserHandle user = UserHandle.ALL;
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -176,11 +175,6 @@ public class PreferencesHelperTest extends UiServiceTestCase {
.build();
}
- private NotificationChannel getDefaultChannel() {
- return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
- IMPORTANCE_LOW);
- }
-
private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
String... channelIds)
throws Exception {
@@ -1787,4 +1781,159 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setEnabled(PKG_N_MR1, 1000, true);
assertEquals(3, mHelper.getBlockedAppCount(0));
}
+
+ @Test
+ public void testSetNotificationDelegate() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testRevokeNotificationDelegate() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testRevokeNotificationDelegate_noDelegateExistsNoCrash() {
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testToggleNotificationDelegate() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+ assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testToggleNotificationDelegate_noDelegateExistsNoCrash() {
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testIsDelegateAllowed_noSource() {
+ assertFalse(mHelper.isDelegateAllowed("does not exist", -1, "whatever", 0));
+ }
+
+ @Test
+ public void testIsDelegateAllowed_noDelegate() {
+ mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+
+ assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "whatever", 0));
+ }
+
+ @Test
+ public void testIsDelegateAllowed_delegateDisabledByApp() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+ }
+
+ @Test
+ public void testIsDelegateAllowed_wrongDelegate() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "banana", 27));
+ }
+
+ @Test
+ public void testIsDelegateAllowed_delegateDisabledByUser() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+ assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+ }
+
+ @Test
+ public void testIsDelegateAllowed() {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+
+ assertTrue(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
+ }
+
+ @Test
+ public void testDelegateXml_noDelegate() throws Exception {
+ mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ loadStreamXml(baos, false);
+
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testDelegateXml_delegate() throws Exception {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ loadStreamXml(baos, false);
+
+ assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testDelegateXml_disabledDelegate() throws Exception {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ loadStreamXml(baos, false);
+
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testDelegateXml_userDisabledDelegate() throws Exception {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ loadStreamXml(baos, false);
+
+ // appears disabled
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+ // but was loaded and can be toggled back on
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+ assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testDelegateXml_entirelyDisabledDelegate() throws Exception {
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
+ mHelper.revokeNotificationDelegate(PKG_O, UID_O);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ loadStreamXml(baos, false);
+
+ // appears disabled
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+ mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
+ assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
+
+ mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
+ assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
+ }
}