diff options
| author | 2019-03-29 15:25:58 +0000 | |
|---|---|---|
| committer | 2019-03-29 15:25:58 +0000 | |
| commit | cb64a683fb9a84e7def8b602aec35672ea749170 (patch) | |
| tree | ffd33d818e08f78b16cdc148873596cc2dcb5844 | |
| parent | 5659644ebf498cb8b52140008d31b9bdef61ab55 (diff) | |
| parent | 0c24500a2fddf4c38f0b32735882aa0d6fd66a88 (diff) | |
Merge "Noti importance from certain Roles cannot be modified"
13 files changed, 795 insertions, 39 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 6f8f310f6a2c..8dff8f405ba6 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -315,7 +315,9 @@ package android.app { } 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 5cdf85a6ca13..69ec831b5d1d 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -170,6 +170,7 @@ public final class NotificationChannel implements Parcelable { private boolean mBlockableSystem = false; private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; + private boolean mImportanceLockedDefaultApp; /** * Creates a notification channel. @@ -656,11 +657,27 @@ public final class NotificationChannel implements Parcelable { * @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 @@ public final class NotificationChannel implements Parcelable { 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 @@ public final class NotificationChannel implements Parcelable { 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 @@ public final class NotificationChannel implements Parcelable { && 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 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, - mImportanceLockedByOEM); + mImportanceLockedByOEM, mImportanceLockedDefaultApp); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1022,6 +1044,7 @@ public final class NotificationChannel implements Parcelable { + ", mBlockableSystem=" + mBlockableSystem + ", mAllowBubbles=" + mAllowBubbles + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + '}'; pw.println(prefix + output); } @@ -1049,6 +1072,7 @@ public final class NotificationChannel implements Parcelable { + ", 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 9630727c8f3d..d287b92876b5 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 8c5f6f24ee79..5ea4636149a2 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 @@ public class NotificationTestHelper { * * @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 0aa103fa27da..8077e3fbaa0a 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.verify; 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 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { 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 9ccd681b17c2..54ec4f23f57f 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 static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL; 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.UriGrantsManager; 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.ShellCallback; 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.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.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.List; 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - 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 @@ public class NotificationManagerService extends SystemService { mAssistants.resetDefaultAssistantsIfNecessary(); } - private void loadPolicyFile() { + @VisibleForTesting + protected void loadPolicyFile() { if (DBG) Slog.d(TAG, "loadPolicyFile"); synchronized (mPolicyFile) { InputStream infile = null; @@ -1530,7 +1538,8 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mDpm = dpm; + mUm = userManager; mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1697,14 +1707,16 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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. @@ -8091,6 +8106,98 @@ public class NotificationManagerService extends SystemService { } } + 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 de9312041c41..4cc08d88bd50 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 @@ public final class NotificationRecord { // 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 660309cd578d..a3e90dcff83e 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.NotificationListenerService; 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 @@ public class PreferencesHelper implements RankingConfig { 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 @@ public class PreferencesHelper implements RankingConfig { channel.setImportanceLockedByOEM(true); } } + channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance); if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { channel.setLockscreenVisibility( NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); @@ -707,6 +711,10 @@ public class PreferencesHelper implements RankingConfig { 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 class PreferencesHelper implements RankingConfig { } } + 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 @@ public class PreferencesHelper implements RankingConfig { 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 aa3135ff18da..3ff85c8806ce 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 8f74571ac8ae..4332feaf24a3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -103,6 +103,7 @@ import android.os.IBinder; 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; @@ -122,9 +123,6 @@ import android.util.ArrayMap; 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; @@ -161,6 +159,9 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -227,23 +228,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @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 @@ -280,11 +279,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Override - UserManagerService getUserManagerService() { - return mUserManagerService; - } - - @Override protected void setNotificationAssistantAccessGrantedForUserInternal( ComponentName assistant, int userId, boolean granted) { if (mNotificationAssistantAccessGrantedCallback != null) { @@ -327,7 +321,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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); @@ -380,7 +374,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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")) { @@ -1928,8 +1922,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testHasCompanionDevice_noService() throws Exception { - mService = new TestableNotificationManagerService(mContext, mUserMangerService); + public void testHasCompanionDevice_noService() { + mService = new TestableNotificationManagerService(mContext); assertFalse(mService.hasCompanionDevice(mListener)); } @@ -2631,7 +2625,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<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, @@ -2655,7 +2649,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<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 e375195af5c9..ee09c7e704c0 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 @@ public class NotificationRecordTest extends UiServiceTestCase { } @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 39e47ecb1b22..87f10a4e077c 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.provider.Settings.Secure; 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.Map; 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 000000000000..91d3e5e4d7f6 --- /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)); + } +} |