Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b94ea47 authored by Felipe Leme's avatar Felipe Leme
Browse files

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
parent 7df723dd
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -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 <a href="{@docRoot}guide/topics/admin/device-admin.html">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.
 *
 * <p><b>Note: </b>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}.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * <p>
@@ -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.
     *
+42 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+76 −0
Original line number Diff line number Diff line
/*
 * 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.
 *
 * <p>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<UnsafeStateException> CREATOR =
            new Creator<UnsafeStateException>() {

        @Override
        public UnsafeStateException createFromParcel(Parcel source) {
            return new UnsafeStateException(source.readInt());
        }

        @Override
        public UnsafeStateException[] newArray(int size) {
            return new UnsafeStateException[size];
        }
    };
}
+15 −0
Original line number Diff line number Diff line
@@ -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}.
     *
     * <p>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() {
    }

+49 −3
Original line number Diff line number Diff line
@@ -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}.
     *
     * <p>Typically called at the beginning of each API method as:
     *
     * <pre><code>
     *
     * if (!canExecute(operation, permission)) return;
     *
     * </code></pre>
     *
     * @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();
    }
Loading