Loading core/java/android/os/IRecoverySystem.aidl +4 −3 Original line number Diff line number Diff line Loading @@ -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); } core/java/android/os/RecoverySystem.java +35 −26 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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"); } } Loading @@ -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 Loading @@ -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"); } } Loading Loading @@ -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"); } } Loading @@ -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"); } Loading services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +174 −44 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; Loading @@ -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 Loading services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java +13 −8 Original line number Diff line number Diff line Loading @@ -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; } Loading services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +130 −34 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/os/IRecoverySystem.aidl +4 −3 Original line number Diff line number Diff line Loading @@ -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); }
core/java/android/os/RecoverySystem.java +35 −26 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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"); } } Loading @@ -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 Loading @@ -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"); } } Loading Loading @@ -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"); } } Loading @@ -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"); } Loading
services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +174 −44 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; Loading @@ -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 Loading
services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java +13 −8 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +130 −34 File changed.Preview size limit exceeded, changes collapsed. Show changes