Keep track of admin actions for DO disclosures
This CL adds bookkeeping to DPMS which will allow us to tell the user
in the Settings UI whether/when the admin requested a bug report or
retrieved logs from the device.
Bug: 32692748
Test: Full DevicePolicyManagerTest unit test coverage; end-to-end tests
will follow as Settings CTS verifier tests
Change-Id: I89728fce4b7e0ff061b354c73caf3742e95a3a3e
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5ca39b0..0196312 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6749,4 +6749,54 @@
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Called by the system to get the time at which the device owner last retrieved security
+ * logging entries.
+ *
+ * @return the time at which the device owner most recently retrieved security logging entries,
+ * in milliseconds since epoch; -1 if security logging entries were never retrieved.
+ *
+ * @hide
+ */
+ public long getLastSecurityLogRetrievalTime() {
+ try {
+ return mService.getLastSecurityLogRetrievalTime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by the system to get the time at which the device owner last requested a bug report.
+ *
+ * @return the time at which the device owner most recently requested a bug report, in
+ * milliseconds since epoch; -1 if a bug report was never requested.
+ *
+ * @hide
+ */
+ public long getLastBugReportRequestTime() {
+ try {
+ return mService.getLastBugReportRequestTime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by the system to get the time at which the device owner last retrieved network logging
+ * events.
+ *
+ * @return the time at which the device owner most recently retrieved network logging events, in
+ * milliseconds since epoch; -1 if network logging events were never retrieved.
+ *
+ * @hide
+ */
+ public long getLastNetworkLogRetrievalTime() {
+ try {
+ return mService.getLastNetworkLogRetrievalTime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a2546c0..d14e0d0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -325,4 +325,8 @@
boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
IApplicationThread caller, IBinder token, in Intent service,
IServiceConnection connection, int flags, int targetUserId);
+
+ long getLastSecurityLogRetrievalTime();
+ long getLastBugReportRequestTime();
+ long getLastNetworkLogRetrievalTime();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 543bba6..0e3d262 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -205,6 +205,12 @@
private static final String TAG_AFFILIATION_ID = "affiliation-id";
+ private static final String TAG_LAST_SECURITY_LOG_RETRIEVAL = "last-security-log-retrieval";
+
+ private static final String TAG_LAST_BUG_REPORT_REQUEST = "last-bug-report-request";
+
+ private static final String TAG_LAST_NETWORK_LOG_RETRIEVAL = "last-network-log-retrieval";
+
private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
private static final String ATTR_VALUE = "value";
@@ -466,6 +472,12 @@
Set<String> mAffiliationIds = new ArraySet<>();
+ long mLastSecurityLogRetrievalTime = -1;
+
+ long mLastBugReportRequestTime = -1;
+
+ long mLastNetworkLogsRetrievalTime = -1;
+
// Used for initialization of users created by createAndManageUsers.
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
@@ -2359,6 +2371,27 @@
out.endTag(null, TAG_AFFILIATION_ID);
}
+ if (policy.mLastSecurityLogRetrievalTime >= 0) {
+ out.startTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+ out.attribute(null, ATTR_VALUE,
+ Long.toString(policy.mLastSecurityLogRetrievalTime));
+ out.endTag(null, TAG_LAST_SECURITY_LOG_RETRIEVAL);
+ }
+
+ if (policy.mLastBugReportRequestTime >= 0) {
+ out.startTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+ out.attribute(null, ATTR_VALUE,
+ Long.toString(policy.mLastBugReportRequestTime));
+ out.endTag(null, TAG_LAST_BUG_REPORT_REQUEST);
+ }
+
+ if (policy.mLastNetworkLogsRetrievalTime >= 0) {
+ out.startTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+ out.attribute(null, ATTR_VALUE,
+ Long.toString(policy.mLastNetworkLogsRetrievalTime));
+ out.endTag(null, TAG_LAST_NETWORK_LOG_RETRIEVAL);
+ }
+
if (policy.mAdminBroadcastPending) {
out.startTag(null, TAG_ADMIN_BROADCAST_PENDING);
out.attribute(null, ATTR_VALUE,
@@ -2515,6 +2548,15 @@
policy.doNotAskCredentialsOnBoot = true;
} else if (TAG_AFFILIATION_ID.equals(tag)) {
policy.mAffiliationIds.add(parser.getAttributeValue(null, "id"));
+ } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
+ policy.mLastSecurityLogRetrievalTime = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_LAST_BUG_REPORT_REQUEST.equals(tag)) {
+ policy.mLastBugReportRequestTime = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_LAST_NETWORK_LOG_RETRIEVAL.equals(tag)) {
+ policy.mLastNetworkLogsRetrievalTime = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_ADMIN_BROADCAST_PENDING.equals(tag)) {
String pending = parser.getAttributeValue(null, ATTR_VALUE);
policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
@@ -5521,9 +5563,18 @@
return false;
}
+ final long currentTime = System.currentTimeMillis();
+ synchronized (this) {
+ DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+ if (currentTime > policyData.mLastBugReportRequestTime) {
+ policyData.mLastBugReportRequestTime = currentTime;
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ }
+
final long callingIdentity = mInjector.binderClearCallingIdentity();
try {
- ActivityManager.getService().requestBugReport(
+ mInjector.getIActivityManager().requestBugReport(
ActivityManager.BUGREPORT_OPTION_REMOTE);
mRemoteBugreportServiceIsActive.set(true);
@@ -6530,6 +6581,12 @@
}
}
+ private void enforceSystemUid() {
+ if (!isCallerWithSystemUid()) {
+ throw new SecurityException("Only the system can call this method.");
+ }
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -9169,6 +9226,15 @@
}
}
+ private synchronized void recordSecurityLogRetrievalTime() {
+ final long currentTime = System.currentTimeMillis();
+ DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+ if (currentTime > policyData.mLastSecurityLogRetrievalTime) {
+ policyData.mLastSecurityLogRetrievalTime = currentTime;
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ }
+
@Override
public ParceledListSlice<SecurityEvent> retrievePreRebootSecurityLogs(ComponentName admin) {
Preconditions.checkNotNull(admin);
@@ -9178,6 +9244,8 @@
return null;
}
+ recordSecurityLogRetrievalTime();
+
ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>();
try {
SecurityLog.readPreviousEvents(output);
@@ -9193,6 +9261,8 @@
Preconditions.checkNotNull(admin);
ensureDeviceOwnerManagingSingleUser(admin);
+ recordSecurityLogRetrievalTime();
+
List<SecurityEvent> logs = mSecurityLogMonitor.retrieveLogs();
return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null;
}
@@ -9668,9 +9738,21 @@
if (mNetworkLogger == null) {
return null;
}
- return isNetworkLoggingEnabledInternalLocked()
- ? mNetworkLogger.retrieveLogs(batchToken)
- : null;
+
+ if (!isNetworkLoggingEnabledInternalLocked()) {
+ return null;
+ }
+
+ final long currentTime = System.currentTimeMillis();
+ synchronized (this) {
+ DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+ if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
+ policyData.mLastNetworkLogsRetrievalTime = currentTime;
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ }
+
+ return mNetworkLogger.retrieveLogs(batchToken);
}
/**
@@ -9714,4 +9796,22 @@
rawIntent.setComponent(info.serviceInfo.getComponentName());
return rawIntent;
}
+
+ @Override
+ public long getLastSecurityLogRetrievalTime() {
+ enforceSystemUid();
+ return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
+ }
+
+ @Override
+ public long getLastBugReportRequestTime() {
+ enforceSystemUid();
+ return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
+ }
+
+ @Override
+ public long getLastNetworkLogRetrievalTime() {
+ enforceSystemUid();
+ return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
+ }
}
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 80be62b..4927f0c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.media.IAudioService;
+import android.net.IIpConnectivityMetrics;
import android.net.Uri;
import android.os.Looper;
import android.os.PowerManagerInternal;
@@ -153,6 +154,11 @@
}
@Override
+ IIpConnectivityMetrics getIIpConnectivityMetrics() {
+ return context.iipConnectivityMetrics;
+ }
+
+ @Override
IWindowManager getIWindowManager() {
return context.iwindowManager;
}
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 56ff621..98a26ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,6 +25,9 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.net.IIpConnectivityMetrics;
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -38,6 +41,7 @@
import android.util.ArraySet;
import android.util.Pair;
+import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -53,6 +57,7 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
@@ -2248,6 +2253,150 @@
assertFalse(dpms.hasUserSetupCompleted());
}
+ private long getLastSecurityLogRetrievalTime() {
+ final long ident = mContext.binder.clearCallingIdentity();
+ final long lastSecurityLogRetrievalTime = dpm.getLastSecurityLogRetrievalTime();
+ mContext.binder.restoreCallingIdentity(ident);
+ return lastSecurityLogRetrievalTime;
+ }
+
+ public void testGetLastSecurityLogRetrievalTime() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(mContext.userManager.getUserCount()).thenReturn(1);
+ when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs))
+ .thenReturn(true);
+
+ // No logs were retrieved so far.
+ assertEquals(-1, getLastSecurityLogRetrievalTime());
+
+ // Enabling logging should not change the timestamp.
+ dpm.setSecurityLoggingEnabled(admin1, true);
+ assertEquals(-1, getLastSecurityLogRetrievalTime());
+
+ // Retrieving the logs should update the timestamp.
+ final long beforeRetrieval = System.currentTimeMillis();
+ dpm.retrieveSecurityLogs(admin1);
+ final long firstSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+ final long afterRetrieval = System.currentTimeMillis();
+ assertTrue(firstSecurityLogRetrievalTime >= beforeRetrieval);
+ assertTrue(firstSecurityLogRetrievalTime <= afterRetrieval);
+
+ // Retrieving the pre-boot logs should update the timestamp.
+ Thread.sleep(2);
+ dpm.retrievePreRebootSecurityLogs(admin1);
+ final long secondSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+ assertTrue(secondSecurityLogRetrievalTime > firstSecurityLogRetrievalTime);
+
+ // Checking the timestamp again should not change it.
+ Thread.sleep(2);
+ assertEquals(secondSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
+
+ // Retrieving the logs again should update the timestamp.
+ dpm.retrieveSecurityLogs(admin1);
+ final long thirdSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+ assertTrue(thirdSecurityLogRetrievalTime > secondSecurityLogRetrievalTime);
+
+ // Disabling logging should not change the timestamp.
+ Thread.sleep(2);
+ dpm.setSecurityLoggingEnabled(admin1, false);
+ assertEquals(thirdSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
+
+ // Restarting the DPMS should not lose the timestamp.
+ initializeDpms();
+ assertEquals(thirdSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
+ }
+
+ private long getLastBugReportRequestTime() {
+ final long ident = mContext.binder.clearCallingIdentity();
+ final long lastBugRequestTime = dpm.getLastBugReportRequestTime();
+ mContext.binder.restoreCallingIdentity(ident);
+ return lastBugRequestTime;
+ }
+
+ public void testGetLastBugReportRequestTime() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(mContext.userManager.getUserCount()).thenReturn(1);
+ mContext.packageName = admin1.getPackageName();
+ mContext.applicationInfo = new ApplicationInfo();
+ when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject()))
+ .thenReturn(Color.WHITE);
+ when(mContext.resources.getColor(eq(R.color.notification_material_background_color),
+ anyObject())).thenReturn(Color.WHITE);
+
+ // No bug reports were requested so far.
+ assertEquals(-1, getLastSecurityLogRetrievalTime());
+
+ // Requesting a bug report should update the timestamp.
+ final long beforeRequest = System.currentTimeMillis();
+ dpm.requestBugreport(admin1);
+ final long bugReportRequestTime = getLastBugReportRequestTime();
+ final long afterRequest = System.currentTimeMillis();
+ assertTrue(bugReportRequestTime >= beforeRequest);
+ assertTrue(bugReportRequestTime <= afterRequest);
+
+ // Checking the timestamp again should not change it.
+ Thread.sleep(2);
+ assertEquals(bugReportRequestTime, getLastBugReportRequestTime());
+
+ // Restarting the DPMS should not lose the timestamp.
+ initializeDpms();
+ assertEquals(bugReportRequestTime, getLastBugReportRequestTime());
+ }
+
+ private long getLastNetworkLogRetrievalTime() {
+ final long ident = mContext.binder.clearCallingIdentity();
+ final long lastNetworkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+ mContext.binder.restoreCallingIdentity(ident);
+ return lastNetworkLogRetrievalTime;
+ }
+
+ public void testGetLastNetworkLogRetrievalTime() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(mContext.userManager.getUserCount()).thenReturn(1);
+ when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
+ .thenReturn(true);
+
+ // No logs were retrieved so far.
+ assertEquals(-1, getLastNetworkLogRetrievalTime());
+
+ // Attempting to retrieve logs without enabling logging should not change the timestamp.
+ dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+ assertEquals(-1, getLastNetworkLogRetrievalTime());
+
+ // Enabling logging should not change the timestamp.
+ dpm.setNetworkLoggingEnabled(admin1, true);
+ assertEquals(-1, getLastNetworkLogRetrievalTime());
+
+ // Retrieving the logs should update the timestamp.
+ final long beforeRetrieval = System.currentTimeMillis();
+ dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+ final long firstNetworkLogRetrievalTime = getLastNetworkLogRetrievalTime();
+ final long afterRetrieval = System.currentTimeMillis();
+ assertTrue(firstNetworkLogRetrievalTime >= beforeRetrieval);
+ assertTrue(firstNetworkLogRetrievalTime <= afterRetrieval);
+
+ // Checking the timestamp again should not change it.
+ Thread.sleep(2);
+ assertEquals(firstNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+
+ // Retrieving the logs again should update the timestamp.
+ dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+ final long secondNetworkLogRetrievalTime = getLastNetworkLogRetrievalTime();
+ assertTrue(secondNetworkLogRetrievalTime > firstNetworkLogRetrievalTime);
+
+ // Disabling logging should not change the timestamp.
+ Thread.sleep(2);
+ dpm.setNetworkLoggingEnabled(admin1, false);
+ assertEquals(secondNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+
+ // Restarting the DPMS should not lose the timestamp.
+ initializeDpms();
+ assertEquals(secondNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
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 37430ad..d74c6dc 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -26,11 +26,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.media.IAudioService;
+import android.net.IIpConnectivityMetrics;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
@@ -249,6 +252,7 @@
public final MockBinder binder;
public final EnvironmentForMock environment;
+ public final Resources resources;
public final SystemPropertiesForMock systemProperties;
public final UserManager userManager;
public final UserManagerInternal userManagerInternal;
@@ -257,6 +261,7 @@
public final PowerManagerForMock powerManager;
public final PowerManagerInternal powerManagerInternal;
public final NotificationManager notificationManager;
+ public final IIpConnectivityMetrics iipConnectivityMetrics;
public final IWindowManager iwindowManager;
public final IActivityManager iactivityManager;
public final IPackageManager ipackageManager;
@@ -278,6 +283,10 @@
public final BuildMock buildMock = new BuildMock();
+ public String packageName = null;
+
+ public ApplicationInfo applicationInfo = null;
+
public DpmMockContext(Context context, File dataDir) {
realTestContext = context;
@@ -286,7 +295,8 @@
binder = new MockBinder();
environment = mock(EnvironmentForMock.class);
- systemProperties= mock(SystemPropertiesForMock.class);
+ resources = mock(Resources.class);
+ systemProperties = mock(SystemPropertiesForMock.class);
userManager = mock(UserManager.class);
userManagerInternal = mock(UserManagerInternal.class);
userManagerForMock = mock(UserManagerForMock.class);
@@ -294,6 +304,7 @@
powerManager = mock(PowerManagerForMock.class);
powerManagerInternal = mock(PowerManagerInternal.class);
notificationManager = mock(NotificationManager.class);
+ iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
iwindowManager = mock(IWindowManager.class);
iactivityManager = mock(IActivityManager.class);
ipackageManager = mock(IPackageManager.class);
@@ -416,6 +427,32 @@
}
@Override
+ public Resources getResources() {
+ return resources;
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return spiedContext.getTheme();
+ }
+
+ @Override
+ public String getPackageName() {
+ if (packageName != null) {
+ return packageName;
+ }
+ return super.getPackageName();
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ if (applicationInfo != null) {
+ return applicationInfo;
+ }
+ return super.getApplicationInfo();
+ }
+
+ @Override
public Object getSystemService(String name) {
switch (name) {
case Context.USER_SERVICE:
@@ -615,4 +652,9 @@
public ContentResolver getContentResolver() {
return contentResolver;
}
+
+ @Override
+ public int getUserId() {
+ return UserHandle.getUserId(binder.getCallingUid());
+ }
}