From 6b94ea477f8f6c29fee301ebbb8e3a3231c3332e Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 3 Nov 2020 14:10:03 -0800 Subject: [PATCH] Initial definition of DevicePolicySafetyChecker. This object will be used to fail DevicePolicyManagers that cannot be executed on automotive when it's not safe to (for example, because the vehicle is moving). Test: atest CarDevicePolicyManagerTest#testLockNow_safe \ CarDevicePolicyManagerTest#testLockNow_unsafe Bug: 172376923 Change-Id: I7f910a7ee5efc7d647525db1687bd27e68cb7c0a --- .../app/admin/DevicePolicyManager.java | 21 +++++ .../app/admin/DevicePolicySafetyChecker.java | 42 ++++++++++ .../app/admin/UnsafeStateException.java | 76 +++++++++++++++++++ .../BaseIDevicePolicyManager.java | 15 ++++ .../DevicePolicyManagerService.java | 52 ++++++++++++- .../java/com/android/server/SystemServer.java | 11 ++- 6 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 core/java/android/app/admin/DevicePolicySafetyChecker.java create mode 100644 core/java/android/app/admin/UnsafeStateException.java diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1d644c43cd0d7..14ba8407dc128 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -122,6 +122,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +// TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it). /** * Public interface for managing policies enforced on a device. Most clients of this class must be * registered with the system as a device @@ -130,6 +131,13 @@ import java.util.concurrent.Executor; * for that method specifies that it is restricted to either device or profile owners. Any * application calling an api may only pass as an argument a device administrator component it * owns. Otherwise, a {@link SecurityException} will be thrown. + * + *

Note: on + * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, some methods can + * throw an {@link UnsafeStateException} exception (for example, if the vehicle is moving), so + * callers running on automotive builds should wrap every method call under the methods provided by + * {@code android.car.admin.CarDevicePolicyManager}. + * *

*

Developer Guides

*

@@ -2443,6 +2451,19 @@ public class DevicePolicyManager { @Retention(RetentionPolicy.SOURCE) public @interface PersonalAppsSuspensionReason {} + /** @hide */ + // TODO(b/172376923): make it TestApi + public static final int OPERATION_LOCK_NOW = 1; + + // TODO(b/172376923) - add all operations + /** @hide */ + @IntDef(prefix = "OPERATION_", value = { + OPERATION_LOCK_NOW, + }) + @Retention(RetentionPolicy.SOURCE) + public static @interface DevicePolicyOperation { + } + /** * Return true if the given administrator component is currently active (enabled) in the system. * diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java new file mode 100644 index 0000000000000..1f8a9335b9ac2 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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 android.app.admin; + +import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager.DevicePolicyOperation; + +/** + * Interface responsible to check if a {@link DevicePolicyManager} API can be safely executed. + * + * @hide + */ +public interface DevicePolicySafetyChecker { + + /** + * Returns whether the given {@code operation} can be safely executed at the moment. + */ + default boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) { + return true; + } + + /** + * Returns a new exception for when the given {@code operation} cannot be safely executed. + */ + @NonNull + default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) { + return new UnsafeStateException(operation); + } +} diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java new file mode 100644 index 0000000000000..d00eb5001f7dc --- /dev/null +++ b/core/java/android/app/admin/UnsafeStateException.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 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 android.app.admin; + +import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager.DevicePolicyOperation; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Exception thrown when a {@link DevicePolicyManager} operation failed because it was not safe + * to be executed at that moment. + * + *

For example, it can be thrown on + * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive devices} when the vehicle + * is moving. + * + * @hide + */ +// TODO(b/172376923): make it public +@SuppressWarnings("serial") +public final class UnsafeStateException extends IllegalStateException implements Parcelable { + + private final @DevicePolicyOperation int mOperation; + + /** @hide */ + public UnsafeStateException(@DevicePolicyOperation int operation) { + super(); + + mOperation = operation; + } + + /** @hide */ + // TODO(b/172376923): make it TestApi + public @DevicePolicyOperation int getOperation() { + return mOperation; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mOperation); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + + @Override + public UnsafeStateException createFromParcel(Parcel source) { + return new UnsafeStateException(source.readInt()); + } + + @Override + public UnsafeStateException[] newArray(int size) { + return new UnsafeStateException[size]; + } + }; +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index eff222a2051a6..ce61d50df1d91 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -15,8 +15,10 @@ */ package com.android.server.devicepolicy; +import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; +import android.util.Slog; import com.android.server.SystemService; @@ -30,6 +32,9 @@ import com.android.server.SystemService; * should be added here to avoid build breakage in downstream branches. */ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { + + private static final String TAG = BaseIDevicePolicyManager.class.getSimpleName(); + /** * To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases. * @@ -55,6 +60,16 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { */ abstract void handleStopUser(int userId); + /** + * Sets the {@link DevicePolicySafetyChecker}. + * + *

Currently, it's called only by {@code SystemServer} on + * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds} + */ + public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { + Slog.w(TAG, "setDevicePolicySafetyChecker() not implemented by " + getClass()); + } + public void clearSystemUpdatePolicyFreezePeriodRecord() { } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 21903921580b9..5fad6f3e28cb0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -135,9 +135,11 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.NetworkEvent; @@ -148,6 +150,7 @@ import android.app.admin.SecurityLog.SecurityEvent; import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; +import android.app.admin.UnsafeStateException; import android.app.backup.IBackupManager; import android.app.trust.TrustManager; import android.app.usage.UsageStatsManagerInternal; @@ -634,6 +637,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @VisibleForTesting final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager; + @Nullable + private DevicePolicySafetyChecker mSafetyChecker; + public static final class Lifecycle extends SystemService { private BaseIDevicePolicyManager mService; @@ -645,8 +651,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { dpmsClassName = DevicePolicyManagerService.class.getName(); } try { - Class serviceClass = Class.forName(dpmsClassName); - Constructor constructor = serviceClass.getConstructor(Context.class); + Class serviceClass = Class.forName(dpmsClassName); + Constructor constructor = serviceClass.getConstructor(Context.class); mService = (BaseIDevicePolicyManager) constructor.newInstance(context); } catch (Exception e) { throw new IllegalStateException( @@ -655,6 +661,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /** Sets the {@link DevicePolicySafetyChecker}. */ + public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { + mService.setDevicePolicySafetyChecker(safetyChecker); + } + @Override public void onStart() { publishBinderService(Context.DEVICE_POLICY_SERVICE, mService); @@ -953,6 +964,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @Override + public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { + Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker.getClass()); + mSafetyChecker = safetyChecker; + } + + /** + * Checks if the feature is supported and it's safe to execute the given {@code operation}. + * + *

Typically called at the beginning of each API method as: + * + *


+     *
+     * if (!canExecute(operation, permission)) return;
+     *
+     * 
+ * + * @return {@code true} when it's safe to execute, {@code false} when the feature is not + * supported or the caller does not have the given {@code requiredPermission}. + * + * @throws UnsafeStateException if it's not safe to execute the operation. + */ + boolean canExecute(@DevicePolicyOperation int operation, @NonNull String requiredPermission) { + if (!mHasFeature && !hasCallingPermission(requiredPermission)) { + return false; + } + if (mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation)) { + return true; + } + throw mSafetyChecker.newUnsafeStateException(operation); + } + /** * Unit test will subclass it to inject mocks. */ @@ -4756,9 +4799,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void lockNow(int flags, boolean parent) { - if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) { + if (!canExecute(DevicePolicyManager.OPERATION_LOCK_NOW, permission.LOCK_DEVICE)) { return; } + final CallerIdentity caller = getCallerIdentity(); final int callingUserId = caller.getUserId(); @@ -8543,6 +8587,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.printf("mIsWatch=%b\n", mIsWatch); pw.printf("mIsAutomotive=%b\n", mIsAutomotive); pw.printf("mHasTelephonyFeature=%b\n", mHasTelephonyFeature); + String safetyChecker = mSafetyChecker == null ? "N/A" : mSafetyChecker.getClass().getName(); + pw.printf("mSafetyChecker=%b\n", safetyChecker); pw.decreaseIndent(); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dfa726f1bfc8d..a1607f4bc56ed 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -34,6 +34,7 @@ import android.app.AppCompatCallbacks; import android.app.ApplicationErrorReport; import android.app.INotificationManager; import android.app.SystemServiceRegistry; +import android.app.admin.DevicePolicySafetyChecker; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; @@ -1468,7 +1469,10 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); - if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { + final DevicePolicyManagerService.Lifecycle dpms; + if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) { + dpms = null; + } else { t.traceBegin("StartLockSettingsService"); try { mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS); @@ -1505,7 +1509,7 @@ public final class SystemServer implements Dumpable { // Always start the Device Policy Manager, so that the API is compatible with // API8. t.traceBegin("StartDevicePolicyManager"); - mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class); + dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class); t.traceEnd(); if (!isWatch) { @@ -2427,6 +2431,9 @@ public final class SystemServer implements Dumpable { if (cshs instanceof Dumpable) { mDumper.addDumpable((Dumpable) cshs); } + if (cshs instanceof DevicePolicySafetyChecker) { + dpms.setDevicePolicySafetyChecker((DevicePolicySafetyChecker) cshs); + } t.traceEnd(); }