MonitoringCertTask no longer relies on software.device_admin
Added a test to validate that it still works the way it should before
and after the change.
Bug: 33258404
Bug: 35196414
Fix: 35129745
Test: runtest -x services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
Test: also manual, instructions:
Test: (1) Disable software.device_admin from tablet_core_hardware, rebuild.
Test: (2) Install CA cert. Notification should appear.
Test: (3) Reboot. Notification should still be there.
Change-Id: Id992725c1844a2fffbde4d8acaba531e99f853ad
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index e566b9d..9981668 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -588,7 +588,7 @@
* @hide for reuse by CertInstaller and Settings.
* @see KeyChain#bind
*/
- public final static class KeyChainConnection implements Closeable {
+ public static class KeyChainConnection implements Closeable {
private final Context context;
private final ServiceConnection serviceConnection;
private final IKeyChainService service;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 003b6d0..590a175 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -208,7 +209,7 @@
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String LOG_TAG = "DevicePolicyManager";
+ protected static final String LOG_TAG = "DevicePolicyManager";
private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
@@ -252,7 +253,6 @@
private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION
= "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
- private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002;
@@ -409,6 +409,7 @@
}
};
+ /** Listens only if mHasFeature == true. */
private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
@Override
@@ -420,6 +421,7 @@
}
};
+ /** Listens only if mHasFeature == true. */
private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
@Override
@@ -513,7 +515,21 @@
final Handler mHandler;
- BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ /** Listens on any device, even when mHasFeature == false. */
+ final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (StorageManager.inCryptKeeperBounce()) {
+ return;
+ }
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
+ .execute(userHandle);
+ }
+ };
+
+ /** Listens only if mHasFeature == true. */
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -559,14 +575,7 @@
}
});
}
- if (Intent.ACTION_USER_UNLOCKED.equals(action)
- || Intent.ACTION_USER_STARTED.equals(action)
- || KeyChain.ACTION_TRUST_STORE_CHANGED.equals(action)) {
- if (!StorageManager.inCryptKeeperBounce()) {
- new MonitoringCertNotificationTask().execute(
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL));
- }
- }
+
if (Intent.ACTION_USER_ADDED.equals(action)) {
sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
synchronized (DevicePolicyManagerService.this) {
@@ -1490,6 +1499,15 @@
mContext = context;
}
+ Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+ final String packageName = mContext.getPackageName();
+ return mContext.createPackageContextAsUser(packageName, 0, user);
+ }
+
+ Resources getResources() {
+ return mContext.getResources();
+ }
+
Owners newOwners() {
return new Owners(getUserManager(), getUserManagerInternal(),
getPackageManagerInternal());
@@ -1725,6 +1743,10 @@
boolean securityLogIsLoggingEnabled() {
return SecurityLog.isLoggingEnabled();
}
+
+ KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException {
+ return KeyChain.bindAsUser(mContext, user);
+ }
}
/**
@@ -1755,18 +1777,27 @@
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+ // Broadcast filter for changes to the trusted certificate store. These changes get a
+ // separate intent filter so we can listen to them even when device_admin is off.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+
if (!mHasFeature) {
// Skip the rest of the initialization
return;
}
- IntentFilter filter = new IntentFilter();
+
+ filter = new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2946,125 +2977,34 @@
}
}
- private class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
- @Override
- protected Void doInBackground(Integer... params) {
- int userHandle = params[0];
+ /**
+ * Remove deleted CA certificates from the "approved" list for a particular user, counting
+ * the number still remaining to approve.
+ *
+ * @param userHandle user to check for. This must be a real user and not, for example,
+ * {@link UserHandle#ALL}.
+ * @param installedCertificates the full set of certificate authorities currently installed for
+ * {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
+ * correspond to some subset of this.
+ *
+ * @return number of certificates yet to be approved by {@param userHandle}.
+ */
+ protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
+ final @NonNull Collection<String> installedCertificates) {
+ enforceManageUsers();
- if (userHandle == UserHandle.USER_ALL) {
- for (UserInfo userInfo : mUserManager.getUsers(true)) {
- manageNotification(userInfo.getUserHandle());
- }
- } else {
- manageNotification(UserHandle.of(userHandle));
- }
- return null;
- }
+ if (!mHasFeature) {
+ return installedCertificates.size();
+ } else {
+ final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
- private void manageNotification(UserHandle userHandle) {
- if (!mUserManager.isUserUnlocked(userHandle)) {
- return;
+ // Remove deleted certificates. Flush xml if necessary.
+ if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
+ saveSettingsLocked(userHandle.getIdentifier());
}
- // Call out to KeyChain to check for CAs which are waiting for approval.
- final List<String> pendingCertificates;
- try {
- pendingCertificates = getInstalledCaCertificates(userHandle);
- } catch (RemoteException | RuntimeException e) {
- Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
- return;
- }
-
- synchronized (DevicePolicyManagerService.this) {
- final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
-
- // Remove deleted certificates. Flush xml if necessary.
- if (policy.mAcceptedCaCertificates.retainAll(pendingCertificates)) {
- saveSettingsLocked(userHandle.getIdentifier());
- }
- // Trim to approved certificates.
- pendingCertificates.removeAll(policy.mAcceptedCaCertificates);
- }
-
- if (pendingCertificates.isEmpty()) {
- mInjector.getNotificationManager().cancelAsUser(
- null, MONITORING_CERT_NOTIFICATION_ID, userHandle);
- return;
- }
-
- // Build and show a warning notification
- int smallIconId;
- String contentText;
- int parentUserId = userHandle.getIdentifier();
- if (getProfileOwner(userHandle.getIdentifier()) != null) {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
- getProfileOwnerName(userHandle.getIdentifier()));
- smallIconId = R.drawable.stat_sys_certificate_info;
- parentUserId = getProfileParentId(userHandle.getIdentifier());
- } else if (getDeviceOwnerUserId() == userHandle.getIdentifier()) {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
- getDeviceOwnerName());
- smallIconId = R.drawable.stat_sys_certificate_info;
- } else {
- contentText = mContext.getString(R.string.ssl_ca_cert_noti_by_unknown);
- smallIconId = android.R.drawable.stat_sys_warning;
- }
-
- final int numberOfCertificates = pendingCertificates.size();
- Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
- dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- dialogIntent.setPackage("com.android.settings");
- dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, numberOfCertificates);
- dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
- PendingIntent notifyIntent = PendingIntent.getActivityAsUser(mContext, 0,
- dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
- new UserHandle(parentUserId));
-
- final Context userContext;
- try {
- final String packageName = mContext.getPackageName();
- userContext = mContext.createPackageContextAsUser(packageName, 0, userHandle);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
- return;
- }
- final Notification noti = new Notification.Builder(userContext)
- .setSmallIcon(smallIconId)
- .setContentTitle(mContext.getResources().getQuantityText(
- R.plurals.ssl_ca_cert_warning, numberOfCertificates))
- .setContentText(contentText)
- .setContentIntent(notifyIntent)
- .setPriority(Notification.PRIORITY_HIGH)
- .setShowWhen(false)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .build();
-
- mInjector.getNotificationManager().notifyAsUser(
- null, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
- }
-
- private List<String> getInstalledCaCertificates(UserHandle userHandle)
- throws RemoteException, RuntimeException {
- KeyChainConnection conn = null;
- try {
- conn = KeyChain.bindAsUser(mContext, userHandle);
- List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
- List<String> result = new ArrayList<>(aliases.size());
- for (int i = 0; i < aliases.size(); i++) {
- result.add(aliases.get(i).string);
- }
- return result;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return null;
- } catch (AssertionError e) {
- throw new RuntimeException(e);
- } finally {
- if (conn != null) {
- conn.close();
- }
- }
+ // Trim approved certificates from the count.
+ return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
}
}
@@ -4629,7 +4569,7 @@
}
saveSettingsLocked(userId);
}
- new MonitoringCertNotificationTask().execute(userId);
+ new MonitoringCertNotificationTask(this, mInjector).execute(userId);
return true;
}
@@ -4653,7 +4593,7 @@
saveSettingsLocked(userInfo.id);
}
- new MonitoringCertNotificationTask().execute(userInfo.id);
+ new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
}
}
}
@@ -7148,7 +7088,7 @@
return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
}
- private int getProfileParentId(int userHandle) {
+ protected int getProfileParentId(int userHandle) {
final long ident = mInjector.binderClearCallingIdentity();
try {
UserInfo parentUser = mUserManager.getProfileParent(userHandle);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
new file mode 100644
index 0000000..03c137a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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.devicepolicy;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
+ protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+ protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
+
+ private final DevicePolicyManagerService mService;
+ private final DevicePolicyManagerService.Injector mInjector;
+
+ public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
+ final DevicePolicyManagerService.Injector injector) {
+ super();
+ mService = service;
+ mInjector = injector;
+ }
+
+ @Override
+ protected Void doInBackground(Integer... params) {
+ int userHandle = params[0];
+
+ if (userHandle == UserHandle.USER_ALL) {
+ for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
+ repostOrClearNotification(userInfo.getUserHandle());
+ }
+ } else {
+ repostOrClearNotification(UserHandle.of(userHandle));
+ }
+ return null;
+ }
+
+ private void repostOrClearNotification(UserHandle userHandle) {
+ if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+ return;
+ }
+
+ // Call out to KeyChain to check for CAs which are waiting for approval.
+ final int pendingCertificateCount;
+ try {
+ pendingCertificateCount = mService.retainAcceptedCertificates(
+ userHandle, getInstalledCaCertificates(userHandle));
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+ return;
+ }
+
+ if (pendingCertificateCount != 0) {
+ showNotification(userHandle, pendingCertificateCount);
+ } else {
+ mInjector.getNotificationManager().cancelAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
+ }
+ }
+
+ private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
+ // Create a context for the target user.
+ final Context userContext;
+ try {
+ userContext = mInjector.createContextAsUser(userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+ return;
+ }
+
+ // Build and show a warning notification
+ int smallIconId;
+ String contentText;
+ int parentUserId = userHandle.getIdentifier();
+ Resources resources = mInjector.getResources();
+ if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getProfileOwnerName(userHandle.getIdentifier()));
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
+ } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getDeviceOwnerName());
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ } else {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
+ smallIconId = android.R.drawable.stat_sys_warning;
+ }
+
+ Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
+ dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ // TODO this next line is taken from original notification code in
+ // {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
+ dialogIntent.setPackage("com.android.settings");
+ dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
+ dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
+ PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
+ dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+ UserHandle.of(parentUserId));
+
+ final Notification noti = new Notification.Builder(userContext)
+ .setSmallIcon(smallIconId)
+ .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
+ pendingCertificateCount))
+ .setContentText(contentText)
+ .setContentIntent(notifyIntent)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setShowWhen(false)
+ .setColor(R.color.system_notification_accent_color)
+ .build();
+
+ mInjector.getNotificationManager().notifyAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
+ }
+
+ private List<String> getInstalledCaCertificates(UserHandle userHandle)
+ throws RemoteException, RuntimeException {
+ try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
+ List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
+ List<String> result = new ArrayList<>(aliases.size());
+ for (int i = 0; i < aliases.size(); i++) {
+ result.add(aliases.get(i).string);
+ }
+ return result;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ } catch (AssertionError e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
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 3b92a34..e6dd13f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -29,6 +29,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Pair;
@@ -375,5 +376,10 @@
boolean isBuildDebuggable() {
return context.buildMock.isDebuggable;
}
+
+ @Override
+ KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
+ return context.keyChainConnection;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 6fb65d5..a186b59 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,8 @@
import android.Manifest.permission;
import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -32,6 +34,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.IIpConnectivityMetrics;
@@ -52,10 +55,13 @@
import android.util.Pair;
import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserRestrictionsUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -76,13 +82,16 @@
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -1193,6 +1202,53 @@
return uid;
}
+ public void testCertificateDisclosure() throws Exception {
+ final int userId = DpmMockContext.CALLER_USER_HANDLE;
+ final UserHandle user = UserHandle.of(userId);
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.callerPermissions.add(permission.MANAGE_USERS);
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ ParceledListSlice<ParcelableString> oneCert = asSlice(new String[] {"1"});
+ ParceledListSlice<ParcelableString> fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
+
+ final String TEST_STRING = "Test for exactly 2 certs out of 4";
+ doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
+
+ // Given that we have exactly one certificate installed,
+ when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
+ // when that certificate is approved,
+ dpms.approveCaCert(oneCert.getList().get(0).string, userId, true);
+ // a notification should not be shown.
+ verify(mContext.notificationManager, timeout(1000))
+ .cancelAsUser(anyString(), anyInt(), eq(user));
+
+ // Given that we have four certificates installed,
+ when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
+ // when two of them are approved (one of them approved twice hence no action),
+ dpms.approveCaCert(fourCerts.getList().get(0).string, userId, true);
+ dpms.approveCaCert(fourCerts.getList().get(1).string, userId, true);
+ // a notification should be shown saying that there are two certificates left to approve.
+ verify(mContext.notificationManager, timeout(1000))
+ .notifyAsUser(anyString(), anyInt(), argThat(
+ new BaseMatcher<Notification>() {
+ @Override
+ public boolean matches(Object item) {
+ final Notification noti = (Notification) item;
+ return TEST_STRING.equals(
+ noti.extras.getString(Notification.EXTRA_TITLE));
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "Notification{title=\"" + TEST_STRING + "\"}");
+ }
+ }), eq(user));
+ }
+
/**
* Simple test for delegate set/get and general delegation. Tests verifying that delegated
* privileges can acually be exercised by a delegate are not covered here.
@@ -3734,4 +3790,20 @@
assertTrue(dpm.setProfileOwner(admin, null, userId));
mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
}
+
+ /**
+ * Convert String[] to ParceledListSlice<ParcelableString>.
+ * <p>
+ * TODO: This shouldn't be necessary. If ParcelableString does need to exist, it also needs
+ * a real constructor.
+ */
+ private static ParceledListSlice<ParcelableString> asSlice(String[] s) {
+ List<ParcelableString> list = new ArrayList<>(s.length);
+ for (int i = 0; i < s.length; i++) {
+ ParcelableString item = new ParcelableString();
+ item.string = s[i];
+ list.add(i, item);
+ }
+ return new ParceledListSlice<ParcelableString>(list);
+ }
}
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 22cd135..46aaf83 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -43,9 +43,11 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
+import android.util.ArrayMap;
import android.view.IWindowManager;
import com.android.internal.widget.LockPatternUtils;
@@ -58,10 +60,12 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -290,6 +294,7 @@
public final TelephonyManager telephonyManager;
public final AccountManager accountManager;
public final AlarmManager alarmManager;
+ public final KeyChain.KeyChainConnection keyChainConnection;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
@@ -300,6 +305,9 @@
public final BuildMock buildMock = new BuildMock();
+ /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
+ public final Map<UserHandle, Context> userContexts = new ArrayMap<>();
+
public String packageName = null;
public ApplicationInfo applicationInfo = null;
@@ -335,6 +343,7 @@
telephonyManager = mock(TelephonyManager.class);
accountManager = mock(AccountManager.class);
alarmManager = mock(AlarmManager.class);
+ keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(context.getPackageManager());
@@ -690,6 +699,19 @@
}
@Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ if (!userContexts.containsKey(user)) {
+ return super.createPackageContextAsUser(packageName, flags, user);
+ }
+ if (!getPackageName().equals(packageName)) {
+ throw new UnsupportedOperationException(
+ "Creating a context as another package is not implemented");
+ }
+ return userContexts.get(user);
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return contentResolver;
}