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

Commit fa7173b8 authored by Tianjie Xu's avatar Tianjie Xu Committed by Gerrit Code Review
Browse files

Merge "Add multi client ror support"

parents 697c19be acea5f3b
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@ interface IRecoverySystem {
    boolean setupBcb(in String command);
    boolean clearBcb();
    void rebootRecoveryWithCommand(in String command);
    boolean requestLskf(in String updateToken, in IntentSender sender);
    boolean clearLskf();
    boolean rebootWithLskf(in String updateToken, in String reason);
    boolean requestLskf(in String packageName, in IntentSender sender);
    boolean clearLskf(in String packageName);
    boolean isLskfCaptured(in String packageName);
    boolean rebootWithLskf(in String packageName, in String reason, in boolean slotSwitch);
}
+35 −26
Original line number Diff line number Diff line
@@ -632,17 +632,11 @@ public class RecoverySystem {
     * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
     * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
     * and ready to apply the OTA.
     * <p>
     * When the system is already prepared for update and this API is called again with the same
     * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock
     * Screen Knowledge Factor.
     * <p>
     * When this API is called again with a different {@code updateToken}, the prepared-for-update
     * status is reset and process repeats as though it's the initial call to this method as
     * described in the first paragraph.
     *
     * @param context the Context to use.
     * @param updateToken token used to indicate which update was prepared
     * @param updateToken this parameter is deprecated and won't be used. See details in
     *                    <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
     *                    TODO(xunchang) update the link of document with the public doc.
     * @param intentSender the intent to call when the update is prepared; may be {@code null}
     * @throws IOException if there were any errors setting up unattended update
     * @hide
@@ -655,7 +649,7 @@ public class RecoverySystem {
            throw new NullPointerException("updateToken == null");
        }
        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
        if (!rs.requestLskf(updateToken, intentSender)) {
        if (!rs.requestLskf(context.getPackageName(), intentSender)) {
            throw new IOException("preparation for update failed");
        }
    }
@@ -673,18 +667,18 @@ public class RecoverySystem {
    public static void clearPrepareForUnattendedUpdate(@NonNull Context context)
            throws IOException {
        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
        if (!rs.clearLskf()) {
        if (!rs.clearLskf(context.getPackageName())) {
            throw new IOException("could not reset unattended update state");
        }
    }

    /**
     * Request that the device reboot and apply the update that has been prepared. The
     * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or
     * this will return {@code false}.
     * Request that the device reboot and apply the update that has been prepared.
     *
     * @param context the Context to use.
     * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before
     * @param updateToken this parameter is deprecated and won't be used. See details in
     *                    <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
     *                    TODO(xunchang) update the link of document with the public doc.
     * @param reason the reboot reason to give to the {@link PowerManager}
     * @throws IOException if the reboot couldn't proceed because the device wasn't ready for an
     *               unattended reboot or if the {@code updateToken} did not match the previously
@@ -699,7 +693,8 @@ public class RecoverySystem {
            throw new NullPointerException("updateToken == null");
        }
        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
        if (!rs.rebootWithLskf(updateToken, reason)) {
        // OTA is the sole user before S, and a slot switch is required for ota update.
        if (!rs.rebootWithLskf(context.getPackageName(), reason, true)) {
            throw new IOException("system not prepared to apply update");
        }
    }
@@ -1283,16 +1278,15 @@ public class RecoverySystem {
    /**
     * Begins the process of asking the user for the Lock Screen Knowledge Factor.
     *
     * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure
     *                    that the preparation was for the correct update
     * @param packageName the package name of the caller who requests resume on reboot
     * @return true if the request was correct
     * @throws IOException if the recovery system service could not be contacted
     */
    private boolean requestLskf(String updateToken, IntentSender sender) throws IOException {
    private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
        try {
            return mService.requestLskf(updateToken, sender);
            return mService.requestLskf(packageName, sender);
        } catch (RemoteException e) {
            throw new IOException("could request update");
            throw new IOException("could request LSKF capture");
        }
    }

@@ -1302,22 +1296,37 @@ public class RecoverySystem {
     * @return true if the setup for OTA was cleared
     * @throws IOException if the recovery system service could not be contacted
     */
    private boolean clearLskf() throws IOException {
    private boolean clearLskf(String packageName) throws IOException {
        try {
            return mService.clearLskf();
            return mService.clearLskf(packageName);
        } catch (RemoteException e) {
            throw new IOException("could not clear LSKF");
        }
    }

    /**
     * Queries if the resume on reboot has been prepared for a given caller.
     *
     * @param packageName the identifier of the caller who requests resume on reboot
     * @return true if resume on reboot is prepared.
     * @throws IOException if the recovery system service could not be contacted
     */
    private boolean isLskfCaptured(String packageName) throws IOException {
        try {
            return mService.isLskfCaptured(packageName);
        } catch (RemoteException e) {
            throw new IOException("could not get LSKF capture state");
        }
    }

    /**
     * Calls the recovery system service to reboot and apply update.
     *
     * @param updateToken the update token for which the update was prepared
     */
    private boolean rebootWithLskf(String updateToken, String reason) throws IOException {
    private boolean rebootWithLskf(String packageName, String reason, boolean slotSwitch)
            throws IOException {
        try {
            return mService.rebootWithLskf(updateToken, reason);
            return mService.rebootWithLskf(packageName, reason, slotSwitch);
        } catch (RemoteException e) {
            throw new IOException("could not reboot for update");
        }
+174 −44
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.server.recoverysystem;

import android.annotation.IntDef;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
@@ -32,6 +34,7 @@ import android.os.ShellCallback;
import android.os.SystemProperties;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.RebootEscrowListener;
@@ -46,6 +49,10 @@ import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * The recovery system service is responsible for coordinating recovery related
@@ -76,9 +83,53 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
    private final Injector mInjector;
    private final Context mContext;

    private boolean mPreparedForReboot;
    private String mUnattendedRebootToken;
    private IntentSender mPreparedForRebootIntentSender;
    @GuardedBy("this")
    private final Map<String, IntentSender> mCallerPendingRequest = new HashMap<>();
    @GuardedBy("this")
    private final Set<String> mCallerPreparedForReboot = new HashSet<>();

    /**
     * Need to prepare for resume on reboot.
     */
    private static final int ROR_NEED_PREPARATION = 0;
    /**
     * Resume on reboot has been prepared, notify the caller.
     */
    private static final int ROR_SKIP_PREPARATION_AND_NOTIFY = 1;
    /**
     * Resume on reboot has been requested. Caller won't be notified until the preparation is done.
     */
    private static final int ROR_SKIP_PREPARATION_NOT_NOTIFY = 2;

    /**
     * The caller never requests for resume on reboot, no need for clear.
     */
    private static final int ROR_NOT_REQUESTED = 0;
    /**
     * Clear the resume on reboot preparation state.
     */
    private static final int ROR_REQUESTED_NEED_CLEAR = 1;
    /**
     * The caller has requested for resume on reboot. No need for clear since other callers may
     * exist.
     */
    private static final int ROR_REQUESTED_SKIP_CLEAR = 2;

    /**
     * The action to perform upon new resume on reboot prepare request for a given client.
     */
    @IntDef({ ROR_NEED_PREPARATION,
            ROR_SKIP_PREPARATION_AND_NOTIFY,
            ROR_SKIP_PREPARATION_NOT_NOTIFY })
    @interface ResumeOnRebootActionsOnRequest {}

    /**
     * The action to perform upon resume on reboot clear request for a given client.
     */
    @IntDef({ROR_NOT_REQUESTED,
            ROR_REQUESTED_NEED_CLEAR,
            ROR_REQUESTED_SKIP_CLEAR})
    @interface ResumeOnRebootActionsOnClear{}

    static class Injector {
        protected final Context mContext;
@@ -286,47 +337,92 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
        }
    }

    private void enforcePermissionForResumeOnReboot() {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
                != PackageManager.PERMISSION_GRANTED
                && mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT)
                        != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Caller or self must have "
                    + android.Manifest.permission.RECOVERY + " or "
                    + android.Manifest.permission.REBOOT + " for resume on reboot.");
        }
    }

    @Override // Binder call
    public boolean requestLskf(String updateToken, IntentSender intentSender) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
    public boolean requestLskf(String packageName, IntentSender intentSender) {
        enforcePermissionForResumeOnReboot();

        if (updateToken == null) {
        if (packageName == null) {
            Slog.w(TAG, "Missing packageName when requesting lskf.");
            return false;
        }

        // No need to prepare again for the same token.
        if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
        @ResumeOnRebootActionsOnRequest int action = updateRoRPreparationStateOnNewRequest(
                packageName, intentSender);
        switch (action) {
            case ROR_SKIP_PREPARATION_AND_NOTIFY:
                // We consider the preparation done if someone else has prepared.
                sendPreparedForRebootIntentIfNeeded(intentSender);
                return true;
        }

        mPreparedForReboot = false;
        mUnattendedRebootToken = updateToken;
        mPreparedForRebootIntentSender = intentSender;

            case ROR_SKIP_PREPARATION_NOT_NOTIFY:
                return true;
            case ROR_NEED_PREPARATION:
                final long origId = Binder.clearCallingIdentity();
                try {
                    mInjector.getLockSettingsService().prepareRebootEscrow();
                } finally {
                    Binder.restoreCallingIdentity(origId);
                }

                return true;
            default:
                throw new IllegalStateException("Unsupported action type on new request " + action);
        }
    }

    // Checks and updates the resume on reboot preparation state.
    private synchronized @ResumeOnRebootActionsOnRequest int updateRoRPreparationStateOnNewRequest(
            String packageName, IntentSender intentSender) {
        if (!mCallerPreparedForReboot.isEmpty()) {
            if (mCallerPreparedForReboot.contains(packageName)) {
                Slog.i(TAG, "RoR already has prepared for " + packageName);
            }

            // Someone else has prepared. Consider the preparation done, and send back the intent.
            mCallerPreparedForReboot.add(packageName);
            return ROR_SKIP_PREPARATION_AND_NOTIFY;
        }

        boolean needPreparation = mCallerPendingRequest.isEmpty();
        if (mCallerPendingRequest.containsKey(packageName)) {
            Slog.i(TAG, "Duplicate RoR preparation request for " + packageName);
        }
        // Update the request with the new intentSender.
        mCallerPendingRequest.put(packageName, intentSender);
        return needPreparation ? ROR_NEED_PREPARATION : ROR_SKIP_PREPARATION_NOT_NOTIFY;
    }

    @Override
    public void onPreparedForReboot(boolean ready) {
        if (mUnattendedRebootToken == null) {
            Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
        if (!ready) {
            return;
        }
        updateRoRPreparationStateOnPreparedForReboot();
    }

        mPreparedForReboot = ready;
        if (ready) {
            sendPreparedForRebootIntentIfNeeded();
    private synchronized void updateRoRPreparationStateOnPreparedForReboot() {
        if (!mCallerPreparedForReboot.isEmpty()) {
            Slog.w(TAG, "onPreparedForReboot called when some clients have prepared.");
        }

        // Send intents to notify callers
        for (Map.Entry<String, IntentSender> entry : mCallerPendingRequest.entrySet()) {
            sendPreparedForRebootIntentIfNeeded(entry.getValue());
            mCallerPreparedForReboot.add(entry.getKey());
        }
        mCallerPendingRequest.clear();
    }

    private void sendPreparedForRebootIntentIfNeeded() {
        final IntentSender intentSender = mPreparedForRebootIntentSender;
    private void sendPreparedForRebootIntentIfNeeded(IntentSender intentSender) {
        if (intentSender != null) {
            try {
                intentSender.sendIntent(null, 0, null, null, null);
@@ -337,37 +433,61 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
    }

    @Override // Binder call
    public boolean clearLskf() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);

        mPreparedForReboot = false;
        mUnattendedRebootToken = null;
        mPreparedForRebootIntentSender = null;
    public boolean clearLskf(String packageName) {
        enforcePermissionForResumeOnReboot();
        if (packageName == null) {
            Slog.w(TAG, "Missing packageName when clearing lskf.");
            return false;
        }

        @ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName);
        switch (action) {
            case ROR_NOT_REQUESTED:
                Slog.w(TAG, "RoR clear called before preparation for caller " + packageName);
                return true;
            case ROR_REQUESTED_SKIP_CLEAR:
                return true;
            case ROR_REQUESTED_NEED_CLEAR:
                final long origId = Binder.clearCallingIdentity();
                try {
                    mInjector.getLockSettingsService().clearRebootEscrow();
                } finally {
                    Binder.restoreCallingIdentity(origId);
                }

                return true;
            default:
                throw new IllegalStateException("Unsupported action type on clear " + action);
        }
    }

    @Override // Binder call
    public boolean rebootWithLskf(String updateToken, String reason) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
    private synchronized @ResumeOnRebootActionsOnClear int updateRoRPreparationStateOnClear(
            String packageName) {
        if (!mCallerPreparedForReboot.contains(packageName) && !mCallerPendingRequest.containsKey(
                packageName)) {
            Slog.w(TAG, packageName + " hasn't prepared for resume on reboot");
            return ROR_NOT_REQUESTED;
        }
        mCallerPendingRequest.remove(packageName);
        mCallerPreparedForReboot.remove(packageName);

        if (!mPreparedForReboot) {
            Slog.i(TAG, "Reboot requested before prepare completed");
            return false;
        // Check if others have prepared ROR.
        boolean needClear = mCallerPendingRequest.isEmpty() && mCallerPreparedForReboot.isEmpty();
        return needClear ? ROR_REQUESTED_NEED_CLEAR : ROR_REQUESTED_SKIP_CLEAR;
    }

        if (updateToken != null && !updateToken.equals(mUnattendedRebootToken)) {
            Slog.i(TAG, "Reboot requested after preparation, but with mismatching token");
    @Override // Binder call
    public boolean rebootWithLskf(String packageName, String reason, boolean slotSwitch) {
        enforcePermissionForResumeOnReboot();
        if (packageName == null) {
            Slog.w(TAG, "Missing packageName when rebooting with lskf.");
            return false;
        }
        if (!isLskfCaptured(packageName)) {
            return false;
        }

        // TODO(xunchang) check the slot to boot into, and fail the reboot upon slot mismatch.
        // TODO(xunchang) write the vbmeta digest along with the escrowKey before reboot.
        if (!mInjector.getLockSettingsService().armRebootEscrow()) {
            Slog.w(TAG, "Failure to escrow key for reboot");
            return false;
@@ -378,6 +498,16 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
        return true;
    }

    @Override // Binder call
    public synchronized boolean isLskfCaptured(String packageName) {
        enforcePermissionForResumeOnReboot();
        if (!mCallerPreparedForReboot.contains(packageName)) {
            Slog.i(TAG, "Reboot requested before prepare completed for caller " + packageName);
            return false;
        }
        return true;
    }

    /**
     * Check if any of the init services is still running. If so, we cannot
     * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
+13 −8
Original line number Diff line number Diff line
@@ -56,26 +56,31 @@ public class RecoverySystemShellCommand extends ShellCommand {
    }

    private int requestLskf() throws RemoteException {
        String updateToken = getNextArgRequired();
        boolean success = mService.requestLskf(updateToken, null);
        String packageName = getNextArgRequired();
        boolean success = mService.requestLskf(packageName, null);
        PrintWriter pw = getOutPrintWriter();
        pw.println("Request LSKF status: " + (success ? "success" : "failure"));
        pw.printf("Request LSKF for packageName: %s, status: %s\n", packageName,
                success ? "success" : "failure");
        return 0;
    }

    private int clearLskf() throws RemoteException {
        boolean success = mService.clearLskf();
        String packageName = getNextArgRequired();
        boolean success = mService.clearLskf(packageName);
        PrintWriter pw = getOutPrintWriter();
        pw.println("Clear LSKF: " + (success ? "success" : "failure"));
        pw.printf("Clear LSKF for packageName: %s, status: %s\n", packageName,
                success ? "success" : "failure");
        return 0;
    }

    private int rebootAndApply() throws RemoteException {
        String updateToken = getNextArgRequired();
        String packageName = getNextArgRequired();
        String rebootReason = getNextArgRequired();
        boolean success = mService.rebootWithLskf(updateToken, rebootReason);
        boolean success = mService.rebootWithLskf(packageName, rebootReason, true);
        PrintWriter pw = getOutPrintWriter();
        pw.println("Reboot and apply status: " + (success ? "success" : "failure"));
        // Keep the old message for cts test.
        pw.printf("%s Reboot and apply status: %s\n", packageName,
                success ? "success" : "failure");
        return 0;
    }

+130 −34

File changed.

Preview size limit exceeded, changes collapsed.