Noti importance from certain Roles cannot be modified
by the notification assistant or user, like the default
phone app.
Fixes: 129358763
Test: atest
Change-Id: I40ef7ff403e2b0d81abe09f15c8804c2d3d2fb8a
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 20b8987..6a70d82 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -84,6 +84,7 @@
import android.Manifest;
import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -107,6 +108,8 @@
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -151,6 +154,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.DeviceConfig;
@@ -216,7 +220,6 @@
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.UserManagerService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -249,6 +252,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@@ -300,6 +304,12 @@
Adjustment.KEY_TEXT_REPLIES,
Adjustment.KEY_USER_SENTIMENT};
+ static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
+ RoleManager.ROLE_DIALER,
+ RoleManager.ROLE_SMS,
+ RoleManager.ROLE_EMERGENCY
+ };
+
// When #matchesCallFilter is called from the ringer, wait at most
// 3s to resolve the contacts. This timeout is required since
// ContactsProvider might take a long time to start up.
@@ -343,6 +353,8 @@
private IDeviceIdleController mDeviceIdleController;
private IUriGrantsManager mUgm;
private UriGrantsManagerInternal mUgmInternal;
+ private RoleObserver mRoleObserver;
+ private UserManager mUm;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -553,18 +565,13 @@
}
}
- UserManagerService getUserManagerService() {
- return UserManagerService.getInstance();
- }
-
void readPolicyXml(InputStream stream, boolean forRestore, int userId)
throws XmlPullParserException, NumberFormatException, IOException {
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);
boolean migratedManagedServices = false;
- boolean ineligibleForManagedServices = forRestore
- && getUserManagerService().isManagedProfile(userId);
+ boolean ineligibleForManagedServices = forRestore && mUm.isManagedProfile(userId);
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
@@ -612,7 +619,8 @@
mAssistants.resetDefaultAssistantsIfNecessary();
}
- private void loadPolicyFile() {
+ @VisibleForTesting
+ protected void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
synchronized (mPolicyFile) {
InputStream infile = null;
@@ -1530,7 +1538,8 @@
NotificationUsageStats usageStats, AtomicFile policyFile,
ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
- IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps) {
+ IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
+ UserManager userManager) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1552,6 +1561,7 @@
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
mDpm = dpm;
+ mUm = userManager;
mHandler = new WorkerHandler(looper);
mRankingThread.start();
@@ -1697,14 +1707,16 @@
AppGlobals.getPackageManager()),
new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
null, snoozeHelper, new NotificationUsageStats(getContext()),
- new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
+ new AtomicFile(new File(
+ systemDir, "notification_policy.xml"), "notification-policy"),
(ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
getGroupHelper(), ActivityManager.getService(),
LocalServices.getService(UsageStatsManagerInternal.class),
LocalServices.getService(DevicePolicyManagerInternal.class),
UriGrantsManager.getService(),
LocalServices.getService(UriGrantsManagerInternal.class),
- (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE));
+ (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
+ getContext().getSystemService(UserManager.class));
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -1827,6 +1839,9 @@
mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mZenModeHelper.onSystemReady();
+ mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
+ getContext().getMainExecutor());
+ mRoleObserver.init();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
// bind to listener services.
@@ -8074,6 +8089,98 @@
}
}
+ class RoleObserver implements OnRoleHoldersChangedListener {
+ // Role name : user id : list of approved packages
+ private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
+
+ private final RoleManager mRm;
+ private final Executor mExecutor;
+
+ RoleObserver(@NonNull RoleManager roleManager,
+ @NonNull @CallbackExecutor Executor executor) {
+ mRm = roleManager;
+ mExecutor = executor;
+ }
+
+ public void init() {
+ List<UserInfo> users = mUm.getUsers();
+ mNonBlockableDefaultApps = new ArrayMap<>();
+ for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
+ final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>();
+ mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList);
+ for (int j = 0; j < users.size(); j++) {
+ Integer userId = users.get(j).getUserHandle().getIdentifier();
+ ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser(
+ NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId)));
+ userToApprovedList.put(userId, approvedForUserId);
+ mPreferencesHelper.updateDefaultApps(userId, null, approvedForUserId);
+ }
+ }
+
+ mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
+ }
+
+ @VisibleForTesting
+ public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
+ return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
+ }
+
+ /**
+ * Convert the assistant-role holder into settings. The rest of the system uses the
+ * settings.
+ *
+ * @param roleName the name of the role whose holders are changed
+ * @param user the user for this role holder change
+ */
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ // we only care about a couple of the roles they'll tell us about
+ boolean relevantChange = false;
+ for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
+ if (NON_BLOCKABLE_DEFAULT_ROLES[i].equals(roleName)) {
+ relevantChange = true;
+ break;
+ }
+ }
+
+ if (!relevantChange) {
+ return;
+ }
+
+ ArraySet<String> roleHolders = new ArraySet<>(mRm.getRoleHoldersAsUser(roleName, user));
+
+ // find the diff
+ ArrayMap<Integer, ArraySet<String>> prevApprovedForRole =
+ mNonBlockableDefaultApps.getOrDefault(roleName, new ArrayMap<>());
+ ArraySet<String> previouslyApproved =
+ prevApprovedForRole.getOrDefault(user.getIdentifier(), new ArraySet<>());
+
+ ArraySet<String> toRemove = new ArraySet<>();
+ ArraySet<String> toAdd = new ArraySet<>();
+
+ for (String previous : previouslyApproved) {
+ if (!roleHolders.contains(previous)) {
+ toRemove.add(previous);
+ }
+ }
+ for (String nowApproved : roleHolders) {
+ if (!previouslyApproved.contains(nowApproved)) {
+ toAdd.add(nowApproved);
+ }
+ }
+
+ // store newly approved apps
+ prevApprovedForRole.put(user.getIdentifier(), roleHolders);
+ mNonBlockableDefaultApps.put(roleName, prevApprovedForRole);
+
+ // update what apps can be blocked
+ mPreferencesHelper.updateDefaultApps(user.getIdentifier(), toRemove, toAdd);
+
+ // RoleManager is the source of truth for this data so we don't need to trigger a
+ // write of the notification policy xml for this change
+ }
+ }
+
public static final class DumpFilter {
public boolean filtered = false;
public String pkgFilter;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index de93120..4cc08d8 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -783,7 +783,8 @@
// Consider Notification Assistant and system overrides to importance. If both, system wins.
if (!getChannel().hasUserSetImportance()
&& mAssistantImportance != IMPORTANCE_UNSPECIFIED
- && !getChannel().isImportanceLockedByOEM()) {
+ && !getChannel().isImportanceLockedByOEM()
+ && !getChannel().isImportanceLockedByCriticalDeviceFunction()) {
mImportance = mAssistantImportance;
mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_ASST;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 660309c..a3e90dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -37,6 +37,8 @@
import android.service.notification.RankingHelperProto;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
@@ -100,6 +102,7 @@
private static final boolean DEFAULT_SHOW_BADGE = true;
private static final boolean DEFAULT_ALLOW_BUBBLE = true;
private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
+ private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false;
/**
* Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
@@ -659,6 +662,7 @@
channel.setImportanceLockedByOEM(true);
}
}
+ channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(
NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
@@ -707,6 +711,10 @@
if (updatedChannel.isImportanceLockedByOEM()) {
updatedChannel.setImportance(channel.getImportance());
}
+ updatedChannel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
+ if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()) {
+ updatedChannel.setImportance(channel.getImportance());
+ }
r.channels.put(updatedChannel.getId(), updatedChannel);
@@ -844,6 +852,26 @@
}
}
+ public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<String> toAdd) {
+ synchronized (mPackagePreferences) {
+ for (PackagePreferences p : mPackagePreferences.values()) {
+ if (userId == UserHandle.getUserId(p.uid)) {
+ if (toRemove != null && toRemove.contains(p.pkg)) {
+ p.defaultAppLockedImportance = false;
+ for (NotificationChannel channel : p.channels.values()) {
+ channel.setImportanceLockedByCriticalDeviceFunction(false);
+ }
+ } else if (toAdd != null && toAdd.contains(p.pkg)) {
+ p.defaultAppLockedImportance = true;
+ for (NotificationChannel channel : p.channels.values()) {
+ channel.setImportanceLockedByCriticalDeviceFunction(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
int uid, String groupId, boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
@@ -1729,8 +1757,11 @@
boolean showBadge = DEFAULT_SHOW_BADGE;
boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+ // these fields are loaded on boot from a different source of truth and so are not
+ // written to notification policy xml
boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
List<String> futureOemLockedChannels = new ArrayList<>();
+ boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();