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";
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);
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);
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);
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";
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();
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);
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);
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";
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);
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);
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);
"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
}
/**
+ * 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.
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);
}
}
}
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;
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_START_ENCRYPTION
= "android.app.action.START_ENCRYPTION";
-
/**
* Widgets are enabled in keyguard
*/
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;
}
/**
+ * 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}.
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;
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);
}
<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" />
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;
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;
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 |
*/
private boolean mHasFeature;
+ private final SecurityLogMonitor mSecurityLogMonitor;
+
private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
|| 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) {
mLocalService = new LocalService();
mLockPatternUtils = injector.newLockPatternUtils();
+ mSecurityLogMonitor = new SecurityLogMonitor(this);
+
mHasFeature = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
if (!mHasFeature) {
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);
void loadOwners() {
synchronized (this) {
mOwners.load();
+ setDeviceOwnerSystemPropertyLocked();
findOwnerComponentIfNecessaryLocked();
migrateUserRestrictionsIfNecessaryLocked();
}
}
+ 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;
}
}
- 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) {
}
}
- 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) {
mOwners.setDeviceOwner(admin, ownerName, userId);
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
+ setDeviceOwnerSystemPropertyLocked();
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
ident = mInjector.binderClearCallingIdentity();
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
+ disableDeviceLoggingIfNotCompliant();
// Reactivate backup service.
long ident = mInjector.binderClearCallingIdentity();
try {
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);
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();
}
}
}
}
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;
+ }
}
--- /dev/null
+/*
+ * 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