Merge "Allow apps to proxy notifications for other apps"

This commit is contained in:
Julia Reynolds
2018-09-11 01:06:11 +00:00
committed by Android (Google) Code Review
9 changed files with 694 additions and 117 deletions

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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.
*
@@ -375,6 +376,42 @@ public class NotificationManager {
notifyAsUser(tag, id, notification, mContext.getUser());
}
/**
* 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
*/
@@ -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) {
@@ -473,6 +515,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.
*

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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();
@@ -2233,6 +2237,60 @@ public class NotificationManagerService extends SystemService {
savePolicyFile();
}
@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 {
@@ -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;
}
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;
}
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";

View File

@@ -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);
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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));
}
}