diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 5ca39b0eb80e4..0196312580fd9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6749,4 +6749,54 @@ public class DevicePolicyManager { 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 a2546c0249628..d14e0d0dfdf0a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -325,4 +325,8 @@ interface IDevicePolicyManager { 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 883d2d81e19c9..c497cb1c44132 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Set 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + 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(), @@ -9171,6 +9228,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + 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 retrievePreRebootSecurityLogs(ComponentName admin) { Preconditions.checkNotNull(admin); @@ -9180,6 +9246,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } + recordSecurityLogRetrievalTime(); + ArrayList output = new ArrayList(); try { SecurityLog.readPreviousEvents(output); @@ -9195,6 +9263,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkNotNull(admin); ensureDeviceOwnerManagingSingleUser(admin); + recordSecurityLogRetrievalTime(); + List logs = mSecurityLogMonitor.retrieveLogs(); return logs != null ? new ParceledListSlice(logs) : null; } @@ -9670,9 +9740,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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); } /** @@ -9716,4 +9798,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 80be62b3f2a2c..4927f0ca873d4 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.IPackageManager; 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; @@ -152,6 +153,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi return context.notificationManager; } + @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 71379b8258e26..e55cafb6730bb 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.ComponentName; 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.test.suitebuilder.annotation.SmallTest; 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 java.util.Set; 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; @@ -2268,6 +2273,150 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 37430ad5dfacb..d74c6dc21c63b 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.ContentResolver; 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 class DpmMockContext extends MockContext { 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 class DpmMockContext extends MockContext { 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 class DpmMockContext extends MockContext { 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 @@ public class DpmMockContext extends MockContext { 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 @@ public class DpmMockContext extends MockContext { 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); @@ -415,6 +426,32 @@ public class DpmMockContext extends MockContext { .thenReturn(isRunning); } + @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) { @@ -615,4 +652,9 @@ public class DpmMockContext extends MockContext { public ContentResolver getContentResolver() { return contentResolver; } + + @Override + public int getUserId() { + return UserHandle.getUserId(binder.getCallingUid()); + } }