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/api/test-current.txt b/api/test-current.txt
index 6f8f310..8dff8f4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -315,7 +315,9 @@
}
public final class NotificationChannel implements android.os.Parcelable {
+ method public boolean isImportanceLockedByCriticalDeviceFunction();
method public boolean isImportanceLockedByOEM();
+ method public void setImportanceLockedByCriticalDeviceFunction(boolean);
method public void setImportanceLockedByOEM(boolean);
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 5cdf85a..69ec831 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -170,6 +170,7 @@
private boolean mBlockableSystem = false;
private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
private boolean mImportanceLockedByOEM;
+ private boolean mImportanceLockedDefaultApp;
/**
* Creates a notification channel.
@@ -656,11 +657,27 @@
* @hide
*/
@TestApi
+ public void setImportanceLockedByCriticalDeviceFunction(boolean locked) {
+ mImportanceLockedDefaultApp = locked;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
public boolean isImportanceLockedByOEM() {
return mImportanceLockedByOEM;
}
/**
+ * @hide
+ */
+ @TestApi
+ public boolean isImportanceLockedByCriticalDeviceFunction() {
+ return mImportanceLockedDefaultApp;
+ }
+
+ /**
* Returns whether the user has chosen the importance of this channel, either to affirm the
* initial selection from the app, or changed it to be higher or lower.
* @see #getImportance()
@@ -834,6 +851,9 @@
out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
}
+ // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
+ // truth and so aren't written to this xml file
+
out.endTag(null, TAG_CHANNEL);
}
@@ -942,7 +962,8 @@
return sb.toString();
}
- public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
+ public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR =
+ new Creator<NotificationChannel>() {
@Override
public NotificationChannel createFromParcel(Parcel in) {
return new NotificationChannel(in);
@@ -983,7 +1004,8 @@
&& Arrays.equals(mVibration, that.mVibration)
&& Objects.equals(getGroup(), that.getGroup())
&& Objects.equals(getAudioAttributes(), that.getAudioAttributes())
- && mImportanceLockedByOEM == that.mImportanceLockedByOEM;
+ && mImportanceLockedByOEM == that.mImportanceLockedByOEM
+ && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp;
}
@Override
@@ -993,7 +1015,7 @@
getUserLockedFields(),
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
- mImportanceLockedByOEM);
+ mImportanceLockedByOEM, mImportanceLockedDefaultApp);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
}
@@ -1022,6 +1044,7 @@
+ ", mBlockableSystem=" + mBlockableSystem
+ ", mAllowBubbles=" + mAllowBubbles
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ '}';
pw.println(prefix + output);
}
@@ -1049,6 +1072,7 @@
+ ", mBlockableSystem=" + mBlockableSystem
+ ", mAllowBubbles=" + mAllowBubbles
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ '}';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9630727c..d287b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -552,6 +552,9 @@
mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification);
}
+ isNonblockable |= mEntry.channel.isImportanceLockedByOEM();
+ isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction();
+
if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
if (mEntry.mIsSystemNotification) {
if (mEntry.channel != null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 8c5f6f2..5ea4636 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -212,7 +212,7 @@
*
* @return a notification with no special properties
*/
- private Notification createNotification() {
+ public Notification createNotification() {
return createNotification(false /* isGroupSummary */, null /* groupKey */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 0aa103f..8077e3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -37,7 +37,9 @@
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.app.Notification;
import android.app.NotificationChannel;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -355,4 +357,30 @@
mGroupRow.setUserExpanded(true);
Assert.assertTrue(mGroupRow.isExpanded());
}
+
+ @Test
+ public void testGetIsNonblockable() throws Exception {
+ ExpandableNotificationRow row =
+ mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+
+ assertFalse(row.getIsNonblockable());
+ }
+
+ @Test
+ public void testGetIsNonblockable_oemLocked() throws Exception {
+ ExpandableNotificationRow row =
+ mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+ row.getEntry().channel.setImportanceLockedByOEM(true);
+
+ assertTrue(row.getIsNonblockable());
+ }
+
+ @Test
+ public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
+ ExpandableNotificationRow row =
+ mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
+ row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true);
+
+ assertTrue(row.getIsNonblockable());
+ }
}
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<>();
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index aa3135f..3ff85c8 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
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 a9eb6ec..00e46a1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -101,6 +101,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -120,9 +121,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
@@ -149,7 +147,6 @@
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -160,6 +157,9 @@
import java.util.Set;
import java.util.function.Consumer;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -226,23 +226,21 @@
@Mock
AppOpsManager mAppOpsManager;
@Mock
- private UserManagerService mUserMangerService;
- @Mock
private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
mNotificationAssistantAccessGrantedCallback;
+ @Mock
+ UserManager mUm;
// Use a Testable subclass so we can simulate calls from the system without failing.
private static class TestableNotificationManagerService extends NotificationManagerService {
int countSystemChecks = 0;
boolean isSystemUid = true;
int countLogSmartSuggestionsVisible = 0;
- UserManagerService mUserManagerService;
@Nullable
NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
- TestableNotificationManagerService(Context context, UserManagerService userManagerService) {
+ TestableNotificationManagerService(Context context) {
super(context);
- mUserManagerService = userManagerService;
}
@Override
@@ -279,11 +277,6 @@
}
@Override
- UserManagerService getUserManagerService() {
- return mUserManagerService;
- }
-
- @Override
protected void setNotificationAssistantAccessGrantedForUserInternal(
ComponentName assistant, int userId, boolean granted) {
if (mNotificationAssistantAccessGrantedCallback != null) {
@@ -326,7 +319,7 @@
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
- mService = new TestableNotificationManagerService(mContext, mUserMangerService);
+ mService = new TestableNotificationManagerService(mContext);
// Use this testable looper.
mTestableLooper = TestableLooper.get(this);
@@ -379,7 +372,7 @@
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
mGroupHelper, mAm, mAppUsageStats,
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
- mAppOpsManager);
+ mAppOpsManager, mUm);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
@@ -1920,8 +1913,8 @@
}
@Test
- public void testHasCompanionDevice_noService() throws Exception {
- mService = new TestableNotificationManagerService(mContext, mUserMangerService);
+ public void testHasCompanionDevice_noService() {
+ mService = new TestableNotificationManagerService(mContext);
assertFalse(mService.hasCompanionDevice(mListener));
}
@@ -2623,7 +2616,7 @@
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- when(mUserMangerService.isManagedProfile(10)).thenReturn(true);
+ when(mUm.isManagedProfile(10)).thenReturn(true);
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
@@ -2647,7 +2640,7 @@
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- when(mUserMangerService.isManagedProfile(10)).thenReturn(false);
+ when(mUm.isManagedProfile(10)).thenReturn(false);
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e375195..ee09c7e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -952,7 +952,31 @@
}
@Test
- public void testApplyImportanceAdjustmentsForNonOemLockedChannels() {
+ public void testIgnoreImportanceAdjustmentsForDefaultAppLockedChannels() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ channel.setImportanceLockedByCriticalDeviceFunction(true);
+
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW);
+ Adjustment adjustment = new Adjustment(
+ PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+ record.addAdjustment(adjustment);
+ record.applyAdjustments();
+ record.calculateImportance();
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+ }
+
+ @Test
+ public void testApplyImportanceAdjustmentsForNonOemDefaultAppLockedChannels() {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
channel.setImportanceLockedByOEM(false);
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 39e47ec..87f10a4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -62,11 +62,9 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Xml;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
@@ -91,6 +89,9 @@
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PreferencesHelperTest extends UiServiceTestCase {
@@ -2442,4 +2443,153 @@
assertEquals(IMPORTANCE_HIGH,
mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
}
+
+ @Test
+ public void testUpdateDefaultApps_add_multiUser() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ // different uids, same package
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
+ mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true);
+
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertFalse(mHelper.getNotificationChannel(
+ PKG_O, UserHandle.PER_USER_RANGE + 1, c.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ }
+
+ @Test
+ public void testUpdateDefaultApps_add_onlyGivenPkg() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
+
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ }
+
+ @Test
+ public void testUpdateDefaultApps_remove() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ // different uids, same package
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+ mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
+
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+
+ ArraySet<String> toRemove = new ArraySet<>();
+ toRemove.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, null);
+
+ assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ }
+
+ @Test
+ public void testUpdateDefaultApps_addAndRemove() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
+
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+
+ // now the default is PKG_N_MR1
+ ArraySet<String> toRemove = new ArraySet<>();
+ toRemove.add(PKG_O);
+ toAdd = new ArraySet<>();
+ toAdd.add(PKG_N_MR1);
+ mHelper.updateDefaultApps(USER.getIdentifier(), toRemove, toAdd);
+
+ assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ }
+
+ @Test
+ public void testUpdateDefaultApps_appDoesNotExist_noCrash() {
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ ArraySet<String> toRemove = new ArraySet<>();
+ toRemove.add(PKG_N_MR1);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, toAdd);
+ }
+
+ @Test
+ public void testUpdateDefaultApps_channelDoesNotExistYet() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+
+ mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+ .isImportanceLockedByCriticalDeviceFunction());
+ }
+
+ @Test
+ public void testUpdateNotificationChannel_defaultAppLockedImportance() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+ ArraySet<String> toAdd = new ArraySet<>();
+ toAdd.add(PKG_O);
+ mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
+
+ NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
+ update.setAllowBubbles(false);
+
+ mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+ assertEquals(IMPORTANCE_HIGH,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+ assertEquals(false,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble());
+
+ mHelper.updateNotificationChannel(PKG_O, UID_O, update, false);
+
+ assertEquals(IMPORTANCE_HIGH,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
new file mode 100644
index 0000000..91d3e5e
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.role.RoleManager.ROLE_DIALER;
+import static android.app.role.RoleManager.ROLE_EMERGENCY;
+import static android.app.role.RoleManager.ROLE_SMS;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
+import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.INotificationManager;
+import android.app.ITransientNotification;
+import android.app.IUriGrantsManager;
+import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.role.RoleManager;
+import android.app.usage.UsageStatsManagerInternal;
+import android.companion.ICompanionDeviceManager;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.NotifyingApp;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestablePermissions;
+import android.text.Html;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+
+import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+import com.android.server.notification.NotificationManagerService.NotificationAssistants;
+import com.android.server.notification.NotificationManagerService.NotificationListeners;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class RoleObserverTest extends UiServiceTestCase {
+ private TestableNotificationManagerService mService;
+ private NotificationManagerService.RoleObserver mRoleObserver;
+
+ private TestableContext mContext = spy(getContext());
+
+ @Mock
+ private PreferencesHelper mPreferencesHelper;
+ @Mock
+ private UserManager mUm;
+ @Mock
+ private Executor mExecutor;
+ @Mock
+ private RoleManager mRoleManager;
+
+ private List<UserInfo> mUsers;
+
+ private static class TestableNotificationManagerService extends NotificationManagerService {
+
+ TestableNotificationManagerService(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void handleSavePolicyFile() {
+ return;
+ }
+
+ @Override
+ protected void loadPolicyFile() {
+ return;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class));
+
+ mUsers = new ArrayList<>();
+ mUsers.add(new UserInfo(0, "system", 0));
+ mUsers.add(new UserInfo(10, "second", 0));
+ when(mUm.getUsers()).thenReturn(mUsers);
+
+ mService = new TestableNotificationManagerService(mContext);
+ mRoleObserver = mService.new RoleObserver(mRoleManager, mExecutor);
+
+ try {
+ mService.init(mock(Looper.class),
+ mock(IPackageManager.class), mock(PackageManager.class),
+ mock(LightsManager.class),
+ mock(NotificationListeners.class), mock(NotificationAssistants.class),
+ mock(ConditionProviders.class), mock(ICompanionDeviceManager.class),
+ mock(SnoozeHelper.class), mock(NotificationUsageStats.class),
+ mock(AtomicFile.class), mock(ActivityManager.class),
+ mock(GroupHelper.class), mock(IActivityManager.class),
+ mock(UsageStatsManagerInternal.class),
+ mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
+ mock(UriGrantsManagerInternal.class),
+ mock(AppOpsManager.class), mUm);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+ mService.setPreferencesHelper(mPreferencesHelper);
+ }
+
+ @Test
+ public void testInit() {
+ List<String> dialer0 = new ArrayList<>();
+ dialer0.add("dialer");
+ List<String> emer0 = new ArrayList<>();
+ emer0.add("emergency");
+ List<String> sms10 = new ArrayList<>();
+ sms10.add("sms");
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(dialer0);
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_EMERGENCY,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(emer0);
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_SMS,
+ mUsers.get(1).getUserHandle())).
+ thenReturn(sms10);
+
+ mRoleObserver.init();
+
+ // verify internal records of current state of the world
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_DIALER, dialer0.get(0), mUsers.get(0).id));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_DIALER, dialer0.get(0), mUsers.get(1).id));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_SMS, dialer0.get(0), mUsers.get(1).id));
+
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id));
+
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_SMS, sms10.get(0), mUsers.get(0).id));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_DIALER, sms10.get(0), mUsers.get(0).id));
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
+ ROLE_SMS, sms10.get(0), mUsers.get(1).id));
+
+ // make sure we're listening to updates
+ verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(
+ eq(mExecutor), any(), eq(UserHandle.ALL));
+
+ // make sure we told pref helper about the state of the world
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(dialer0));
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(emer0));
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(10, null, new ArraySet<>(sms10));
+ }
+
+ @Test
+ public void testSwapDefault() {
+ List<String> dialer0 = new ArrayList<>();
+ dialer0.add("dialer");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(dialer0);
+
+ mRoleObserver.init();
+
+ List<String> newDefault = new ArrayList<>();
+ newDefault.add("phone");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(newDefault);
+
+ mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
+
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(
+ 0, new ArraySet<>(dialer0), new ArraySet<>(newDefault));
+ }
+
+ @Test
+ public void testSwapDefault_multipleOverlappingApps() {
+ List<String> dialer0 = new ArrayList<>();
+ dialer0.add("dialer");
+ dialer0.add("phone");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(dialer0);
+
+ mRoleObserver.init();
+
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0));
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+
+ List<String> newDefault = new ArrayList<>();
+ newDefault.add("phone");
+ newDefault.add("emerPhone");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(newDefault);
+
+ mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
+
+ ArraySet<String> expectedRemove = new ArraySet<>();
+ expectedRemove.add("dialer");
+ ArraySet<String> expectedAdd = new ArraySet<>();
+ expectedAdd.add("emerPhone");
+
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(
+ 0, expectedRemove, expectedAdd);
+
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0));
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0));
+ assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+ }
+
+ @Test
+ public void testSwapDefault_newUser() {
+ List<String> dialer0 = new ArrayList<>();
+ dialer0.add("dialer");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(0).getUserHandle())).
+ thenReturn(dialer0);
+
+ mRoleObserver.init();
+
+ List<String> dialer10 = new ArrayList<>();
+ dialer10.add("phone");
+
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_DIALER,
+ mUsers.get(1).getUserHandle())).
+ thenReturn(dialer10);
+
+ mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(10));
+
+ ArraySet<String> expectedRemove = new ArraySet<>();
+ ArraySet<String> expectedAdd = new ArraySet<>();
+ expectedAdd.add("phone");
+
+ verify(mPreferencesHelper, times(1)).updateDefaultApps(
+ 10, expectedRemove, expectedAdd);
+
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10));
+ assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
+ }
+}