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

Commit bca12f47 authored by Pavel Grafov's avatar Pavel Grafov
Browse files

Allow COPE DPC to confirm compliance explicitly

Currently when COPE PO sets a maximum managed profile time off
policy, it is sufficient for the user to turn the profile on
briefly to reset the timer, which technically allows the user
to circumvent the policy, doing so repeatedly.

With this change DPC can control when the timer gets reset by
acknowledging compliance explicitly.
By default, the behavior is the same as before unless the DPC
overrides DAR#onComplianceAcknowledgementRequired in which case
it will have to call DPM.acknowledgeDeviceCompliant when the
timer can be safely reset, e.g. after a successful policy sync.

Bug: 181943978
Test: atest OrgOwnedProfileOwnerTest#testWorkProfileMaximumTimeOff_complianceRequiredBroadcastDefault
Test: atest OrgOwnedProfileOwnerTest#testWorkProfileMaximumTimeOff_complianceRequiredBroadcastOverride
Test: atest OrgOwnedProfileOwnerTest#testWorkProfileMaximumTimeOff
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Change-Id: I6efea53aad8097c047f1e3ebf62b421dc32214e6
parent 30c71e62
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -6983,6 +6983,7 @@ package android.app.admin {
    method public void onBugreportShared(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
    method public void onBugreportSharingDeclined(@NonNull android.content.Context, @NonNull android.content.Intent);
    method @Nullable public String onChoosePrivateKeyAlias(@NonNull android.content.Context, @NonNull android.content.Intent, int, @Nullable android.net.Uri, @Nullable String);
    method public void onComplianceAcknowledgementRequired(@NonNull android.content.Context, @NonNull android.content.Intent);
    method @Nullable public CharSequence onDisableRequested(@NonNull android.content.Context, @NonNull android.content.Intent);
    method public void onDisabled(@NonNull android.content.Context, @NonNull android.content.Intent);
    method public void onEnabled(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -7037,6 +7038,7 @@ package android.app.admin {
  }
  public class DevicePolicyManager {
    method public void acknowledgeDeviceCompliant();
    method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
    method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
    method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -7155,6 +7157,7 @@ package android.app.admin {
    method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName);
    method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
    method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
    method public boolean isComplianceAcknowledgementRequired();
    method public boolean isDeviceIdAttestationSupported();
    method public boolean isDeviceOwnerApp(String);
    method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
+35 −0
Original line number Diff line number Diff line
@@ -523,6 +523,16 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
    public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED =
            "android.app.action.OPERATION_SAFETY_STATE_CHANGED";

    /**
     * Broadcast action: notify the profile owner on an organization-owned device that it needs to
     * acknowledge device compliance.
     *
     * @hide
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED =
            "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED";

    /**
     * An {@code int} extra specifying an {@link OperationSafetyReason}.
     *
@@ -1116,6 +1126,29 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
        onOperationSafetyStateChanged(context, reason, isSafe);
    }

    /**
     * Called to notify a profile owner of an organization-owned device that it needs to acknowledge
     * device compliance to allow the user to turn the profile off if needed according to the
     * maximum profile time off policy.
     *
     * Default implementation acknowledges compliance immediately. DPC may prefer to override this
     * implementation to delay acknowledgement until a successful policy sync. Until compliance is
     * acknowledged the user is still free to turn the profile off, but the timer won't be reset,
     * so personal apps will be suspended sooner. This callback is delivered using a foreground
     * broadcast and should be handled quickly.
     *
     * @param context the running context as per {@link #onReceive}
     * @param intent The received intent as per {@link #onReceive}.
     *
     * @see DevicePolicyManager#acknowledgeDeviceCompliant()
     * @see DevicePolicyManager#isComplianceAcknowledgementRequired()
     * @see DevicePolicyManager#setManagedProfileMaximumTimeOff(ComponentName, long)
     */
    public void onComplianceAcknowledgementRequired(
            @NonNull Context context, @NonNull Intent intent) {
        getManager(context).acknowledgeDeviceCompliant();
    }

    private boolean hasRequiredExtra(Intent intent, String extra) {
        if (intent.hasExtra(extra)) return true;

@@ -1204,6 +1237,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
                    intent.getParcelableExtra(Intent.EXTRA_USER));
        } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
            onOperationSafetyStateChanged(context, intent);
        } else if (ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED.equals(action)) {
            onComplianceAcknowledgementRequired(context, intent);
        }
    }
}
+57 −0
Original line number Diff line number Diff line
@@ -13376,6 +13376,63 @@ public class DevicePolicyManager {
        return 0;
    }
    /**
     * Called by a profile owner of an organization-owned managed profile to acknowledge that the
     * device is compliant and the user can turn the profile off if needed according to the maximum
     * time off policy.
     *
     * This method should be called when the device is deemed compliant after getting
     * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback in
     * case it is overridden. Before this method is called the user is still free to turn the
     * profile off, but the timer won't be reset, so personal apps will be suspended sooner.
     *
     * DPCs only need acknowledging device compliance if they override
     * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)}, otherwise
     * compliance is acknowledged automatically.
     *
     * @throws IllegalStateException if the user isn't unlocked
     * @see #isComplianceAcknowledgementRequired()
     * @see #setManagedProfileMaximumTimeOff(ComponentName, long)
     * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)
     */
    public void acknowledgeDeviceCompliant() {
        throwIfParentInstance("acknowledgeDeviceCompliant");
        if (mService != null) {
            try {
                mService.acknowledgeDeviceCompliant();
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }
    }
    /**
     * Called by a profile owner of an organization-owned managed profile to query whether it needs
     * to acknowledge device compliance to allow the user to turn the profile off if needed
     * according to the maximum profile time off policy.
     *
     * Normally when acknowledgement is needed the DPC gets a
     * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback.
     * But if the callback was not delivered or handled for some reason, this method can be used to
     * verify if acknowledgement is needed.
     *
     * @throws IllegalStateException if the user isn't unlocked
     * @see #acknowledgeDeviceCompliant()
     * @see #setManagedProfileMaximumTimeOff(ComponentName, long)
     * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)
     */
    public boolean isComplianceAcknowledgementRequired() {
        throwIfParentInstance("isComplianceAcknowledgementRequired");
        if (mService != null) {
            try {
                return mService.isComplianceAcknowledgementRequired();
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }
        return false;
    }
    /**
     * Returns {@code true} when {@code userId} has a profile owner that is capable of resetting
     * password in RUNNING_LOCKED state. For that it should have at least one direct boot aware
+4 −0
Original line number Diff line number Diff line
@@ -495,6 +495,10 @@ interface IDevicePolicyManager {

    long getManagedProfileMaximumTimeOff(in ComponentName admin);
    void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs);

    void acknowledgeDeviceCompliant();
    boolean isComplianceAcknowledgementRequired();

    boolean canProfileOwnerResetPasswordWhenLocked(int userId);

    void setNextOperationSafety(int operation, int reason);
+8 −0
Original line number Diff line number Diff line
@@ -109,6 +109,14 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
        return 0;
    }

    @Override
    public void acknowledgeDeviceCompliant() {}

    @Override
    public boolean isComplianceAcknowledgementRequired() {
        return false;
    }

    public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
        return false;
    }
Loading