diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 94b434c9b2eb3..89e974e7d7387 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3615,6 +3615,48 @@ public class DevicePolicyManager { return null; } + /** + * Called by a device owner to get the list of apps to keep around as APKs even if no user has + * currently installed it. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * + * @return List of package names to keep cached. + * @hide + */ + public List getKeepUninstalledPackages(@NonNull ComponentName admin) { + if (mService != null) { + try { + return mService.getKeepUninstalledPackages(admin); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return null; + } + + /** + * Called by a device owner to set a list of apps to keep around as APKs even if no user has + * currently installed it. + * + *

Please note that setting this policy does not imply that specified apps will be + * automatically pre-cached.

+ * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageNames List of package names to keep cached. + * @hide + */ + public void setKeepUninstalledPackages(@NonNull ComponentName admin, + @NonNull List packageNames) { + if (mService != null) { + try { + mService.setKeepUninstalledPackages(admin, packageNames); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + /** * Called by a device owner to create a user with the specified name. The UserHandle returned * by this method should not be persisted as user handles are recycled as users are removed and diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 93a5503f7e9e1..c43fa9a276408 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -231,4 +231,6 @@ interface IDevicePolicyManager { String permission, int grantState); int getPermissionGrantState(in ComponentName admin, String packageName, String permission); boolean isProvisioningAllowed(String action); + void setKeepUninstalledPackages(in ComponentName admin,in List packageList); + List getKeepUninstalledPackages(in ComponentName admin); } diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index bf70d6ca9dfe6..905ac5e093d8d 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -16,7 +16,7 @@ package android.content.pm; -import android.annotation.NonNull; +import java.util.List; /** * Package manager local system service interface. @@ -115,4 +115,11 @@ public abstract class PackageManagerInternal { */ public abstract void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId); + + /** + * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has + * currently installed it. The apps are not preloaded. + * @param packageList List of package names to keep cached. + */ + public abstract void setKeepUninstalledPackages(List packageList); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4f3544b3e380a..21a42063cca52 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -617,6 +617,9 @@ public class PackageManagerService extends IPackageManager.Stub { final DefaultPermissionGrantPolicy mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this); + // List of packages names to keep cached, even if they are uninstalled for all users + private List mKeepUninstalledPackages; + private static class IFVerificationParams { PackageParser.Package pkg; boolean replacing; @@ -13015,7 +13018,7 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0; final int[] users = deleteAllUsers ? sUserManager.getUserIds() : new int[]{ userId }; if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "deletePackage for user " + userId); } @@ -13090,6 +13093,10 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } + private boolean shouldKeepUninstalledPackageLPr(String packageName) { + return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName); + } + /** * This method is an internal method that could be get invoked either * to delete an installed package or to clean up a failed installation. @@ -13512,7 +13519,9 @@ public class PackageManagerService extends IPackageManager.Stub { false, // blockUninstall ps.readUserState(userId).domainVerificationStatus, 0); if (!isSystemApp(ps)) { - if (ps.isAnyInstalled(sUserManager.getUserIds())) { + // Do not uninstall the APK if an app should be cached + boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName); + if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) { // Other user still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. @@ -16686,15 +16695,21 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_CLEAN_APKS) { Slog.i(TAG, "Checking package " + packageName); } - boolean keep = false; - for (int i = 0; i < users.length; i++) { - if (users[i] != userHandle && ps.getInstalled(users[i])) { - keep = true; - if (DEBUG_CLEAN_APKS) { - Slog.i(TAG, " Keeping package " + packageName + " for user " - + users[i]); + boolean keep = shouldKeepUninstalledPackageLPr(packageName); + if (keep) { + if (DEBUG_CLEAN_APKS) { + Slog.i(TAG, " Keeping package " + packageName + " - requested by DO"); + } + } else { + for (int i = 0; i < users.length; i++) { + if (users[i] != userHandle && ps.getInstalled(users[i])) { + keep = true; + if (DEBUG_CLEAN_APKS) { + Slog.i(TAG, " Keeping package " + packageName + " for user " + + users[i]); + } + break; } - break; } } if (!keep) { @@ -16896,6 +16911,23 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void deletePackageIfUnusedLPr(final String packageName) { + PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps == null) { + return; + } + if (!ps.isAnyInstalled(sUserManager.getUserIds())) { + // TODO Implement atomic delete if package is unused + // It is currently possible that the package will be deleted even if it is installed + // after this method returns. + mHandler.post(new Runnable() { + public void run() { + deletePackageX(packageName, 0, PackageManager.DELETE_ALL_USERS); + } + }); + } + } + /** * Check and throw if the given before/after packages would be considered a * downgrade. @@ -17133,6 +17165,34 @@ public class PackageManagerService extends IPackageManager.Stub { packageName, userId); } } + + @Override + public void setKeepUninstalledPackages(final List packageList) { + Preconditions.checkNotNull(packageList); + List removedFromList = null; + synchronized (mPackages) { + if (mKeepUninstalledPackages != null) { + final int packagesCount = mKeepUninstalledPackages.size(); + for (int i = 0; i < packagesCount; i++) { + String oldPackage = mKeepUninstalledPackages.get(i); + if (packageList != null && packageList.contains(oldPackage)) { + continue; + } + if (removedFromList == null) { + removedFromList = new ArrayList<>(); + } + removedFromList.add(oldPackage); + } + } + mKeepUninstalledPackages = new ArrayList<>(packageList); + if (removedFromList != null) { + final int removedCount = removedFromList.size(); + for (int i = 0; i < removedCount; i++) { + deletePackageIfUnusedLPr(removedFromList.get(i)); + } + } + } + } } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 642def3c6860a..4c1580915949e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -60,6 +60,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; @@ -416,7 +417,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "cross-profile-widget-providers"; private static final String TAG_PROVIDER = "provider"; private static final String TAG_PACKAGE_LIST_ITEM = "item"; - + private static final String TAG_KEEP_UNINSTALLED_PACKAGES = "keep-uninstalled-packages"; private static final String TAG_USER_RESTRICTIONS = "user-restrictions"; final DeviceAdminInfo info; @@ -489,6 +490,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // allowed. List permittedInputMethods; + // List of package names to keep cached. + List keepUninstalledPackages; + // TODO: review implementation decisions with frameworks team boolean specifiesGlobalProxy = false; String globalProxySpec = null; @@ -674,6 +678,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES, permittedAccessiblityServices); writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods); + writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages); if (hasUserRestrictions()) { UserRestrictionsUtils.writeRestrictions( out, userRestrictions, TAG_USER_RESTRICTIONS); @@ -787,6 +792,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { permittedAccessiblityServices = readPackageList(parser, tag); } else if (TAG_PERMITTED_IMES.equals(tag)) { permittedInputMethods = readPackageList(parser, tag); + } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) { + keepUninstalledPackages = readPackageList(parser, tag); } else if (TAG_USER_RESTRICTIONS.equals(tag)) { UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions()); } else { @@ -981,13 +988,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(disabledKeyguardFeatures); pw.print(prefix); pw.print("crossProfileWidgetProviders="); pw.println(crossProfileWidgetProviders); - if (!(permittedAccessiblityServices == null)) { + if (permittedAccessiblityServices != null) { pw.print(prefix); pw.print("permittedAccessibilityServices="); - pw.println(permittedAccessiblityServices.toString()); + pw.println(permittedAccessiblityServices); } - if (!(permittedInputMethods == null)) { + if (permittedInputMethods != null) { pw.print(prefix); pw.print("permittedInputMethods="); - pw.println(permittedInputMethods.toString()); + pw.println(permittedInputMethods); + } + if (keepUninstalledPackages != null) { + pw.print(prefix); pw.print("keepUninstalledPackages="); + pw.println(keepUninstalledPackages); } pw.print(prefix); pw.println("userRestrictions:"); UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", userRestrictions); @@ -1068,6 +1079,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return LocalServices.getService(UserManagerInternal.class); } + PackageManagerInternal getPackageManagerInternal() { + return LocalServices.getService(PackageManagerInternal.class); + } + NotificationManager getNotificationManager() { return mContext.getSystemService(NotificationManager.class); } @@ -2101,6 +2116,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new SetupContentObserver(mHandler).register(mContext.getContentResolver()); // Initialize the user setup state, to handle the upgrade case. updateUserSetupComplete(); + + List packageList; + synchronized (this) { + packageList = getKeepUninstalledPackagesLocked(); + } + if (packageList != null) { + mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList); + } } private void ensureDeviceOwnerUserStarted() { @@ -4489,6 +4512,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void setKeepUninstalledPackages(ComponentName who, List packageList) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkNotNull(packageList, "packageList is null"); + final int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { + ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + admin.keepUninstalledPackages = packageList; + saveSettingsLocked(userHandle); + mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList); + } + } + + @Override + public List getKeepUninstalledPackages(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); + if (!mHasFeature) { + return null; + } + // TODO In split system user mode, allow apps on user 0 to query the list + synchronized (this) { + // Check if this is the device owner who is calling + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + return getKeepUninstalledPackagesLocked(); + } + } + + private List getKeepUninstalledPackagesLocked() { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + return (deviceOwner != null) ? deviceOwner.keepUninstalledPackages : null; + } + @Override public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) { if (!mHasFeature) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 2c01b8aaac12c..56d6fc05d6b52 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -20,8 +20,8 @@ import com.android.internal.widget.LockPatternUtils; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; -import android.content.Context; import android.content.pm.IPackageManager; +import android.content.pm.PackageManagerInternal; import android.media.IAudioService; import android.os.Looper; import android.os.PowerManagerInternal; @@ -112,6 +112,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi return context.userManagerInternal; } + @Override + PackageManagerInternal getPackageManagerInternal() { + return context.packageManagerInternal; + } + @Override PowerManagerInternal getPowerManagerInternal() { return context.powerManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index f4fdc95870ea5..bb1e06ddb35a9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.media.IAudioService; import android.os.Bundle; @@ -205,6 +206,7 @@ public class DpmMockContext extends MockContext { public final SystemPropertiesForMock systemProperties; public final UserManager userManager; public final UserManagerInternal userManagerInternal; + public final PackageManagerInternal packageManagerInternal; public final UserManagerForMock userManagerForMock; public final PowerManagerForMock powerManager; public final PowerManagerInternal powerManagerInternal; @@ -237,6 +239,7 @@ public class DpmMockContext extends MockContext { userManager = mock(UserManager.class); userManagerInternal = mock(UserManagerInternal.class); userManagerForMock = mock(UserManagerForMock.class); + packageManagerInternal = mock(PackageManagerInternal.class); powerManager = mock(PowerManagerForMock.class); powerManagerInternal = mock(PowerManagerInternal.class); notificationManager = mock(NotificationManager.class); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java deleted file mode 100644 index 56fd351887e30..0000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2015 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.pm; - -import com.android.server.devicepolicy.DpmTestUtils; - -import android.os.Bundle; -import android.os.UserManager; -import android.test.AndroidTestCase; -import android.test.MoreAsserts; - -/** - * Tests for {@link com.android.server.pm.UserRestrictionsUtils}. - * - *

Run with:

-   m FrameworksServicesTests &&
-   adb install \
-     -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
-   adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
-     -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
- * 
- */ -public class UserRestrictionsUtilsTest extends AndroidTestCase { - public void testNonNull() { - Bundle out = UserRestrictionsUtils.nonNull(null); - assertNotNull(out); - out.putBoolean("a", true); // Should not be Bundle.EMPTY. - - Bundle in = new Bundle(); - assertSame(in, UserRestrictionsUtils.nonNull(in)); - } - - public void testIsEmpty() { - assertTrue(UserRestrictionsUtils.isEmpty(null)); - assertTrue(UserRestrictionsUtils.isEmpty(new Bundle())); - assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a"))); - } - - public void testClone() { - Bundle in = new Bundle(); - Bundle out = UserRestrictionsUtils.clone(in); - assertNotSame(in, out); - DpmTestUtils.assertRestrictions(out, new Bundle()); - - out = UserRestrictionsUtils.clone(null); - assertNotNull(out); - out.putBoolean("a", true); // Should not be Bundle.EMPTY. - } - - public void testMerge() { - Bundle a = DpmTestUtils.newRestrictions("a", "d"); - Bundle b = DpmTestUtils.newRestrictions("b", "d", "e"); - - UserRestrictionsUtils.merge(a, b); - - DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a); - - UserRestrictionsUtils.merge(a, null); - - DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a); - - try { - UserRestrictionsUtils.merge(a, a); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - public void testIsSystemControlled() { - assertTrue(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_RECORD_AUDIO)); - assertFalse(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_FUN)); - } - - public void testCanDeviceOwnerChange() { - assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO)); - assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER)); - assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER)); - } - - public void testCanProfileOwnerChange() { - assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO)); - assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER)); - assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER)); - assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME)); - } - - public void testSortToGlobalAndLocal() { - final Bundle local = new Bundle(); - final Bundle global = new Bundle(); - - UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local); - assertEquals(0, global.size()); - assertEquals(0, local.size()); - - UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local); - assertEquals(0, global.size()); - assertEquals(0, local.size()); - - UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions( - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL - ), global, local); - - - DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions( - // These can be set by PO too, but when DO sets them, they're global. - UserManager.DISALLOW_ADJUST_VOLUME, - UserManager.DISALLOW_UNMUTE_MICROPHONE, - - // These can only be set by DO. - UserManager.DISALLOW_USB_FILE_TRANSFER, - UserManager.DISALLOW_CONFIG_TETHERING - ), global); - - DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions( - // They can be set by both DO/PO. - UserManager.DISALLOW_OUTGOING_BEAM, - UserManager.DISALLOW_APPS_CONTROL - ), local); - } - - public void testAreEqual() { - assertTrue(UserRestrictionsUtils.areEqual( - null, - null)); - - assertTrue(UserRestrictionsUtils.areEqual( - null, - Bundle.EMPTY)); - - assertTrue(UserRestrictionsUtils.areEqual( - Bundle.EMPTY, - null)); - - assertTrue(UserRestrictionsUtils.areEqual( - Bundle.EMPTY, - Bundle.EMPTY)); - - assertTrue(UserRestrictionsUtils.areEqual( - new Bundle(), - Bundle.EMPTY)); - - assertFalse(UserRestrictionsUtils.areEqual( - null, - DpmTestUtils.newRestrictions("a"))); - - assertFalse(UserRestrictionsUtils.areEqual( - DpmTestUtils.newRestrictions("a"), - null)); - - assertTrue(UserRestrictionsUtils.areEqual( - DpmTestUtils.newRestrictions("a"), - DpmTestUtils.newRestrictions("a"))); - - assertFalse(UserRestrictionsUtils.areEqual( - DpmTestUtils.newRestrictions("a"), - DpmTestUtils.newRestrictions("a", "b"))); - - assertFalse(UserRestrictionsUtils.areEqual( - DpmTestUtils.newRestrictions("a", "b"), - DpmTestUtils.newRestrictions("a"))); - - assertFalse(UserRestrictionsUtils.areEqual( - DpmTestUtils.newRestrictions("b", "a"), - DpmTestUtils.newRestrictions("a", "a"))); - - // Make sure false restrictions are handled correctly. - final Bundle a = DpmTestUtils.newRestrictions("a"); - a.putBoolean("b", true); - - final Bundle b = DpmTestUtils.newRestrictions("a"); - b.putBoolean("b", false); - - assertFalse(UserRestrictionsUtils.areEqual(a, b)); - assertFalse(UserRestrictionsUtils.areEqual(b, a)); - } -}