OSDN Git Service

Add DevicePolicyManager APIs for process logging.
authorRubin Xu <rubinxu@google.com>
Mon, 11 Jan 2016 12:11:35 +0000 (12:11 +0000)
committerRubin Xu <rubinxu@google.com>
Mon, 1 Feb 2016 10:28:24 +0000 (10:28 +0000)
Add Device Owner APIs for controlling and retrieving the logs. Retrieving the
logs should be rate limited unless we are at the risk of losing logs due to
constrained buffer space.

Bug: 22860162
Change-Id: I80658f5a14e86d7cfd42402fbc5e98dc11698c0e

api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/admin/DeviceAdminReceiver.java
core/java/android/app/admin/DevicePolicyManager.java
core/java/android/app/admin/IDevicePolicyManager.aidl
core/res/AndroidManifest.xml
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java [new file with mode: 0644]

index 0a20a73..26b7aa9 100644 (file)
@@ -5783,6 +5783,7 @@ package android.app.admin {
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
+    method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
@@ -5829,6 +5830,7 @@ package android.app.admin {
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public boolean getDeviceLoggingEnabled(android.content.ComponentName);
     method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -5885,6 +5887,8 @@ package android.app.admin {
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrieveDeviceLogs(android.content.ComponentName);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrievePreviousDeviceLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
     method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
@@ -5896,6 +5900,7 @@ package android.app.admin {
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean);
     method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
index e4ddbdf..d46decf 100644 (file)
@@ -5919,6 +5919,7 @@ package android.app.admin {
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
+    method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
@@ -5967,6 +5968,7 @@ package android.app.admin {
     method public int getCurrentFailedPasswordAttempts();
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
+    method public boolean getDeviceLoggingEnabled(android.content.ComponentName);
     method public java.lang.String getDeviceOwner();
     method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.lang.String getDeviceOwnerNameOnAnyUser();
@@ -6030,6 +6032,8 @@ package android.app.admin {
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrieveDeviceLogs(android.content.ComponentName);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrievePreviousDeviceLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
     method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException;
     method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
@@ -6042,6 +6046,7 @@ package android.app.admin {
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean);
     method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
index 2348747..6d9004d 100644 (file)
@@ -5785,6 +5785,7 @@ package android.app.admin {
     method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
     method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
     method public void onReceive(android.content.Context, android.content.Intent);
+    method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
@@ -5831,6 +5832,7 @@ package android.app.admin {
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public boolean getDeviceLoggingEnabled(android.content.ComponentName);
     method public java.lang.String getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -5887,6 +5889,8 @@ package android.app.admin {
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrieveDeviceLogs(android.content.ComponentName);
+    method public java.util.List<android.auditing.SecurityLog.SecurityEvent> retrievePreviousDeviceLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
     method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
@@ -5898,6 +5902,7 @@ package android.app.admin {
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean);
     method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
index 3c1ecc7..34dfb26 100644 (file)
@@ -266,6 +266,14 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
             "android.app.action.BUGREPORT_SHARE";
 
     /**
+     * Broadcast action: notify that a new batch of device logs is ready to be collected.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SECURITY_LOGS_AVAILABLE
+            = "android.app.action.SECURITY_LOGS_AVAILABLE";
+
+    /**
      * A string containing the SHA-256 hash of the bugreport file.
      *
      * @see #ACTION_BUGREPORT_SHARE
@@ -596,6 +604,18 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
     }
 
     /**
+     * Called when a new batch of device logs can be retrieved.
+     *
+     * <p>This callback is only applicable to device owners.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @see DevicePolicyManager#retrieveDeviceLogs(ComponentName)
+     */
+    public void onSecurityLogsAvailable(Context context, Intent intent) {
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -647,6 +667,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
             int failureCode = intent.getIntExtra(EXTRA_BUGREPORT_FAILURE_REASON,
                     BUGREPORT_FAILURE_FAILED_COMPLETING);
             onBugreportFailed(context, intent, failureCode);
+        } else if (ACTION_SECURITY_LOGS_AVAILABLE.equals(action)) {
+            onSecurityLogsAvailable(context, intent);
         }
     }
 }
index c79c407..f04e76e 100644 (file)
@@ -23,12 +23,15 @@ import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.app.Activity;
+import android.auditing.SecurityLog;
+import android.auditing.SecurityLog.SecurityEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
@@ -2210,7 +2213,6 @@ public class DevicePolicyManager {
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_START_ENCRYPTION
             = "android.app.action.START_ENCRYPTION";
-
     /**
      * Widgets are enabled in keyguard
      */
@@ -5352,6 +5354,66 @@ public class DevicePolicyManager {
                 throw new SecurityException("The current user does not have a parent profile.");
             }
             return new DevicePolicyManager(mContext, true);
+        } catch (RemoteException e) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+            return null;
+        }
+    }
+
+    /**
+     * Called by device owner to control the device logging feature. Logging can only be
+     * enabled on single user devices where the sole user is managed by the device owner.
+     *
+     * <p> Device logs contain various information intended for security auditing purposes.
+     * See {@link SecurityEvent} for details.
+     *
+     * @param admin Which device owner this request is associated with.
+     * @param enabled whether device logging should be enabled or not.
+     * @see #retrieveDeviceLogs
+     */
+    public void setDeviceLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
+        try {
+            mService.setDeviceLoggingEnabled(admin, enabled);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+        }
+    }
+
+    /**
+     * Return whether device logging is enabled or not by the device owner.
+     *
+     * @param admin Which device owner this request is associated with.
+     * @return {@code true} if device logging is enabled by device owner, {@code false} otherwise.
+     */
+    public boolean getDeviceLoggingEnabled(@NonNull ComponentName admin) {
+        try {
+            return mService.getDeviceLoggingEnabled(admin);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return false;
+        }
+    }
+
+    /**
+     * Called by device owner to retrieve all new device logging entries since the last call to
+     * this API after device boots.
+     *
+     * <p> Access to the logs is rate limited and it will only return new logs after the device
+     * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
+     *
+     * @param admin Which device owner this request is associated with.
+     * @return the new batch of device logs which is a list of {@link SecurityEvent},
+     * or {@code null} if rate limitation is exceeded or if logging is currently disabled.
+     */
+    public List<SecurityEvent> retrieveDeviceLogs(@NonNull ComponentName admin) {
+        try {
+            ParceledListSlice<SecurityEvent> list = mService.retrieveDeviceLogs(admin);
+            if (list != null) {
+                return list.getList();
+            } else {
+                // Rate limit exceeded.
+                return null;
+            }
         } catch (RemoteException re) {
             Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
             return null;
@@ -5375,6 +5437,28 @@ public class DevicePolicyManager {
     }
 
     /**
+     * Called by device owners to retrieve device logs from before the device's last reboot.
+     *
+     * <p>
+     * <strong> The device logs are retrieved from a RAM region which is not guaranteed to be
+     * corruption-free during power cycles, due to hardware variations and limitations. As a
+     * result, this API is provided as best-effort and the returned logs may contain corrupted data.
+     * </strong>
+     *
+     * @param admin Which device owner this request is associated with.
+     * @return Device logs from before the latest reboot of the system.
+     */
+    public List<SecurityEvent> retrievePreviousDeviceLogs(@NonNull ComponentName admin) {
+        try {
+            ParceledListSlice<SecurityEvent> list = mService.retrievePreviousDeviceLogs(admin);
+            return list.getList();
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return Collections.<SecurityEvent>emptyList();
+        }
+    }
+
+    /**
      * Called by a profile owner of a managed profile to set the color used for customization.
      * This color is used as background color of the confirm credentials screen for that user.
      * The default color is {@link android.graphics.Color#GRAY}.
index 37d13e5..b57e1b7 100644 (file)
@@ -21,6 +21,7 @@ import android.app.admin.SystemUpdatePolicy;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
 import android.net.ProxyInfo;
 import android.net.Uri;
@@ -285,4 +286,9 @@ interface IDevicePolicyManager {
 
     void setAffiliationIds(in ComponentName admin, in List<String> ids);
     boolean isAffiliatedUser();
+
+    void setDeviceLoggingEnabled(in ComponentName admin, boolean enabled);
+    boolean getDeviceLoggingEnabled(in ComponentName admin);
+    ParceledListSlice retrieveDeviceLogs(in ComponentName admin);
+    ParceledListSlice retrievePreviousDeviceLogs(in ComponentName admin);
 }
index a04146f..3ef81ba 100644 (file)
     <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
     <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
     <protected-broadcast android:name="android.intent.action.MEDIA_RESOURCE_GRANTED" />
+    <protected-broadcast android:name="android.app.action.SECURITY_LOGS_AVAILABLE" />
 
     <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" />
     <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL" />
index 7831c4d..83aa64e 100644 (file)
@@ -50,6 +50,7 @@ import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.backup.IBackupManager;
 import android.auditing.SecurityLog;
+import android.auditing.SecurityLog.SecurityEvent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -63,6 +64,7 @@ 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.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -210,6 +212,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
             = "application-restrictions-manager";
 
+    /**
+     *  System property whose value is either "true" or "false", indicating whether
+     */
+    private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.device_owner";
+
     private static final int STATUS_BAR_DISABLE_MASK =
             StatusBarManager.DISABLE_EXPAND |
             StatusBarManager.DISABLE_NOTIFICATION_ICONS |
@@ -287,6 +294,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
      */
     private boolean mHasFeature;
 
+    private final SecurityLogMonitor mSecurityLogMonitor;
+
     private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
     private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
 
@@ -438,7 +447,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                     || KeyChain.ACTION_STORAGE_CHANGED.equals(action)) {
                 new MonitoringCertNotificationTask().execute(intent);
             }
-            if (Intent.ACTION_USER_REMOVED.equals(action)) {
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
+                disableDeviceLoggingIfNotCompliant();
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                disableDeviceLoggingIfNotCompliant();
                 removeUserData(userHandle);
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 synchronized (DevicePolicyManagerService.this) {
@@ -1470,6 +1482,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         mLocalService = new LocalService();
         mLockPatternUtils = injector.newLockPatternUtils();
 
+        mSecurityLogMonitor = new SecurityLogMonitor(this);
+
         mHasFeature = mContext.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
         if (!mHasFeature) {
@@ -1479,6 +1493,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         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(KeyChain.ACTION_STORAGE_CHANGED);
@@ -1559,6 +1574,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     void loadOwners() {
         synchronized (this) {
             mOwners.load();
+            setDeviceOwnerSystemPropertyLocked();
             findOwnerComponentIfNecessaryLocked();
             migrateUserRestrictionsIfNecessaryLocked();
 
@@ -1568,6 +1584,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
     }
 
+    private void setDeviceOwnerSystemPropertyLocked() {
+        // Device owner may still be provisioned, do not set the read-only system property yet.
+        if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+            return;
+        }
+        // Still at the first stage of CryptKeeper double bounce, mOwners.hasDeviceOwner is
+        // always false at this point.
+        if ("encrypted".equals(mInjector.systemPropertiesGet("ro.crypto.state"))
+                && "trigger_restart_min_framework".equals(
+                        mInjector.systemPropertiesGet("vold.decrypt"))){
+            return;
+        }
+
+        if (!TextUtils.isEmpty(mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT))) {
+            Slog.wtf(LOG_TAG, "Trying to set ro.device_owner, but it has already been set?");
+        } else {
+            if (mOwners.hasDeviceOwner()) {
+                mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
+                disableDeviceLoggingIfNotCompliant();
+                if (SecurityLog.getLoggingEnabledProperty()) {
+                    mSecurityLogMonitor.start();
+                }
+            } else {
+                mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false");
+            }
+        }
+    }
+
     private void findOwnerComponentIfNecessaryLocked() {
         if (!mOwners.hasDeviceOwner()) {
             return;
@@ -4897,29 +4941,37 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
     }
 
-    private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
+    private boolean isDeviceOwnerManagedSingleUserDevice() {
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!mOwners.hasDeviceOwner()) {
+                return false;
+            }
         }
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
             if (mInjector.userManagerIsSplitSystemUser()) {
                 // In split system user mode, only allow the case where the device owner is managing
                 // the only non-system user of the device
-                if (mUserManager.getUserCount() > 2
-                        || mOwners.getDeviceOwnerUserId() == UserHandle.USER_SYSTEM) {
-                    throw new SecurityException(
-                            "There should only be one user, managed by Device Owner");
-                }
-            } else if (mUserManager.getUserCount() > 1) {
-                throw new SecurityException(
-                        "There should only be one user, managed by Device Owner");
+                return (mUserManager.getUserCount() == 2
+                        && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM);
+            } else  {
+                return mUserManager.getUserCount() == 1;
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(callingIdentity);
         }
     }
 
+    private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+        if (!isDeviceOwnerManagedSingleUserDevice()) {
+            throw new SecurityException(
+                    "There should only be one user, managed by Device Owner");
+        }
+    }
+
     @Override
     public boolean requestBugreport(ComponentName who) {
         if (!mHasFeature) {
@@ -4961,7 +5013,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
     }
 
-    private synchronized void sendDeviceOwnerCommand(String action, Bundle extras) {
+    synchronized void sendDeviceOwnerCommand(String action, Bundle extras) {
         Intent intent = new Intent(action);
         intent.setComponent(mOwners.getDeviceOwnerComponent());
         if (extras != null) {
@@ -5305,6 +5357,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
             mOwners.setDeviceOwner(admin, ownerName, userId);
             mOwners.writeDeviceOwner();
             updateDeviceOwnerLocked();
+            setDeviceOwnerSystemPropertyLocked();
             Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
 
             ident = mInjector.binderClearCallingIdentity();
@@ -5436,6 +5489,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
             mOwners.clearDeviceOwner();
             mOwners.writeDeviceOwner();
             updateDeviceOwnerLocked();
+            disableDeviceLoggingIfNotCompliant();
             // Reactivate backup service.
             long ident = mInjector.binderClearCallingIdentity();
             try {
@@ -7561,6 +7615,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
 
         private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
                 Settings.Secure.USER_SETUP_COMPLETE);
+        private final Uri mDeviceProvisioned = Settings.Global.getUriFor(
+                Settings.Global.DEVICE_PROVISIONED);
 
         public SetupContentObserver(Handler handler) {
             super(handler);
@@ -7568,12 +7624,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
 
         void register(ContentResolver resolver) {
             resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL);
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
             if (mUserSetupComplete.equals(uri)) {
                 updateUserSetupComplete();
+            } else if (mDeviceProvisioned.equals(uri)) {
+                // Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
+                // is delayed until device is marked as provisioned.
+                setDeviceOwnerSystemPropertyLocked();
             }
         }
     }
@@ -8253,4 +8314,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
         }
         return false;
     }
+
+    private void disableDeviceLoggingIfNotCompliant() {
+        if (!isDeviceOwnerManagedSingleUserDevice()) {
+            SecurityLog.setLoggingEnabledProperty(false);
+            Slog.w(LOG_TAG, "Device logging turned off as it's no longer a single user device.");
+        }
+    }
+
+    @Override
+    public void setDeviceLoggingEnabled(ComponentName admin, boolean enabled) {
+        Preconditions.checkNotNull(admin);
+        ensureDeviceOwnerManagingSingleUser(admin);
+
+        synchronized (this) {
+            SecurityLog.setLoggingEnabledProperty(enabled);
+            if (enabled) {
+                mSecurityLogMonitor.start();
+            } else {
+                mSecurityLogMonitor.stop();
+            }
+        }
+    }
+
+    @Override
+    public boolean getDeviceLoggingEnabled(ComponentName admin) {
+        Preconditions.checkNotNull(admin);
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            return SecurityLog.getLoggingEnabledProperty();
+        }
+    }
+
+    @Override
+    public ParceledListSlice<SecurityEvent> retrievePreviousDeviceLogs(ComponentName admin) {
+        Preconditions.checkNotNull(admin);
+        ensureDeviceOwnerManagingSingleUser(admin);
+
+        ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>();
+        try {
+            SecurityLog.readPreviousEvents(output);
+            return new ParceledListSlice<SecurityEvent>(output);
+        } catch (IOException e) {
+            Slog.w(LOG_TAG, "Fail to read previous events" , e);
+            return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList());
+        }
+    }
+
+    @Override
+    public ParceledListSlice<SecurityEvent> retrieveDeviceLogs(ComponentName admin) {
+        Preconditions.checkNotNull(admin);
+        ensureDeviceOwnerManagingSingleUser(admin);
+
+        List<SecurityEvent> logs = mSecurityLogMonitor.retrieveLogs();
+        return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
new file mode 100644 (file)
index 0000000..f2d6180
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2016 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.admin.DeviceAdminReceiver;
+import android.auditing.SecurityLog;
+import android.auditing.SecurityLog.SecurityEvent;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import android.os.Process;
+
+/**
+ * A class managing access to the security logs. It maintains an internal buffer of pending
+ * logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via
+ * JNI binding, and kept until device owner has retrieved to prevent loss of logs. Access to
+ * the logs from the device owner is rate-limited, and device owner is notified when the logs
+ * are ready to be retrieved. This happens every two hours, or when our internal buffer is
+ * larger than a certain threshold.
+ */
+class SecurityLogMonitor implements Runnable {
+    private final DevicePolicyManagerService mService;
+
+    SecurityLogMonitor(DevicePolicyManagerService service) {
+        mService = service;
+    }
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SecurityLogMonitor";
+    /**
+     * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N}
+     * it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device
+     * Owner.
+     */
+    private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
+    /**
+     * The maximum number of entries we should store before dropping earlier logs, to limit the
+     * memory usage.
+     */
+    private static final int BUFFER_ENTRIES_MAXIMUM_LEVEL = BUFFER_ENTRIES_NOTIFICATION_LEVEL * 10;
+    /**
+     * How often should Device Owner be notified under normal circumstances.
+     */
+    private static final long RATE_LIMIT_INTERVAL_MILLISECONDS = TimeUnit.HOURS.toMillis(2);
+    /**
+     * Internally how often should the monitor poll the security logs from logd.
+     */
+    private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+
+    @GuardedBy("this")
+    private Thread mMonitorThread = null;
+    @GuardedBy("this")
+    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
+    @GuardedBy("this")
+    private boolean mAllowedToRetrieve = false;
+    // When DO will be allowed to retrieves the log, in milliseconds.
+    @GuardedBy("this")
+    private long mNextAllowedRetrivalTimeMillis = -1;
+
+    synchronized void start() {
+        if (mMonitorThread == null) {
+            mPendingLogs = new ArrayList<SecurityEvent>();
+            mAllowedToRetrieve = false;
+            mNextAllowedRetrivalTimeMillis = -1;
+
+            mMonitorThread = new Thread(this);
+            mMonitorThread.start();
+        }
+    }
+
+    synchronized void stop() {
+        if (mMonitorThread != null) {
+            mMonitorThread.interrupt();
+            try {
+                mMonitorThread.join(TimeUnit.SECONDS.toMillis(5));
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Interrupted while waiting for thread to stop", e);
+            }
+            mMonitorThread = null;
+        }
+    }
+
+    /**
+     * Returns the new batch of logs since the last call to this method. Returns null if
+     * rate limit is exceeded.
+     */
+    synchronized List<SecurityEvent> retrieveLogs() {
+        if (mAllowedToRetrieve) {
+            mAllowedToRetrieve = false;
+            mNextAllowedRetrivalTimeMillis = System.currentTimeMillis()
+                    + RATE_LIMIT_INTERVAL_MILLISECONDS;
+            List<SecurityEvent> result = mPendingLogs;
+            mPendingLogs = new ArrayList<SecurityEvent>();
+            return result;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void run() {
+        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+        ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
+        // The timestamp of the latest log entry that has been read, in nanoseconds
+        long lastLogTimestampNanos = -1;
+        while (!Thread.currentThread().isInterrupted()) {
+            try {
+                Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
+
+                if (lastLogTimestampNanos < 0) {
+                    // Non-blocking read that returns all logs immediately.
+                    if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
+                    SecurityLog.readEvents(logs);
+                } else {
+                    if (DEBUG) Slog.d(TAG,
+                            "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
+                    // Non-blocking read that returns all logs >= the  timestamp immediately.
+                    SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
+                }
+                if (!logs.isEmpty()) {
+                    if (DEBUG) Slog.d(TAG, "processing new logs");
+                    synchronized (this) {
+                        mPendingLogs.addAll(logs);
+                        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
+                            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
+                            mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
+                                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
+                                    mPendingLogs.size()));
+                        }
+                    }
+                    lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
+                    logs.clear();
+                }
+                notifyDeviceOwnerIfNeeded();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to read security log", e);
+            } catch (InterruptedException e) {
+                Log.i(TAG, "Thread interrupted, exiting.", e);
+                // We are asked to stop.
+                break;
+            }
+        }
+        if (DEBUG) Slog.d(TAG, "MonitorThread exit.");
+        synchronized (this) {
+            // Reset state and clear buffer
+            mPendingLogs = new ArrayList<SecurityEvent>();
+            mAllowedToRetrieve = false;
+            mNextAllowedRetrivalTimeMillis = -1;
+        }
+    }
+
+    private void notifyDeviceOwnerIfNeeded() {
+        boolean shouldNotifyDO = false;
+        boolean allowToRetrieveNow = false;
+        synchronized (this) {
+            int logSize = mPendingLogs.size();
+            if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) {
+                // Allow DO to retrieve logs if too many pending logs
+                allowToRetrieveNow = true;
+            } else if (logSize > 0) {
+                if (mNextAllowedRetrivalTimeMillis == -1 ||
+                        System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) {
+                    // Rate limit reset
+                    allowToRetrieveNow = true;
+                }
+            }
+            shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow;
+            mAllowedToRetrieve = allowToRetrieveNow;
+        }
+        if (shouldNotifyDO) {
+            if (DEBUG) Slog.d(TAG, "notify DO");
+            mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE,
+                    null);
+        }
+    }
+}
\ No newline at end of file