diff options
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | core/java/android/os/IRecoverySystem.aidl | 1 | ||||
-rw-r--r-- | core/java/android/os/RecoverySystem.java | 15 | ||||
-rw-r--r-- | services/core/java/com/android/server/recoverysystem/RecoverySystemService.java | 80 |
4 files changed, 97 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp index 02db27a174a9..8b93deccf96f 100644 --- a/Android.bp +++ b/Android.bp @@ -211,6 +211,7 @@ java_library { "apex_aidl_interface-java", "framework-protos", "updatable-driver-protos", + "ota_metadata_proto_java", "android.hidl.base-V1.0-java", "android.hardware.cas-V1.0-java", "android.hardware.cas-V1.1-java", diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl index 9368b68a91c6..88bdb7f6f00b 100644 --- a/core/java/android/os/IRecoverySystem.aidl +++ b/core/java/android/os/IRecoverySystem.aidl @@ -23,6 +23,7 @@ import android.os.IRecoverySystemProgressListener; /** @hide */ interface IRecoverySystem { + boolean allocateSpaceForUpdate(in String packageFilePath); boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener); boolean setupBcb(in String command); boolean clearBcb(); diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 051447f9219b..944b71700450 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -672,6 +672,14 @@ public class RecoverySystem { if (!rs.setupBcb(command)) { throw new IOException("Setup BCB failed"); } + try { + if (!rs.allocateSpaceForUpdate(packageFile)) { + throw new IOException("Failed to allocate space for update " + + packageFile.getAbsolutePath()); + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } // Having set up the BCB (bootloader control block), go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); @@ -1392,6 +1400,13 @@ public class RecoverySystem { } /** + * Talks to RecoverySystemService via Binder to allocate space + */ + private boolean allocateSpaceForUpdate(File packageFile) throws RemoteException { + return mService.allocateSpaceForUpdate(packageFile.getAbsolutePath()); + } + + /** * Talks to RecoverySystemService via Binder to clear up the BCB. */ private boolean clearBcb() { diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index 24337f3ad346..12e55e5f1805 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -24,10 +24,13 @@ import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMA import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED; import static android.os.RecoverySystem.ResumeOnRebootRebootErrorCode; import static android.os.UserHandle.USER_SYSTEM; +import static android.ota.nano.OtaPackageMetadata.ApexMetadata; import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE; import android.annotation.IntDef; +import android.apex.CompressedApexInfo; +import android.apex.CompressedApexInfoList; import android.content.Context; import android.content.IntentSender; import android.content.SharedPreferences; @@ -47,9 +50,11 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; import android.provider.DeviceConfig; +import android.sysprop.ApexProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.FastImmutableArraySet; +import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -59,6 +64,7 @@ import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.RebootEscrowListener; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.ApexManager; import libcore.io.IoUtils; @@ -68,9 +74,13 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * The recovery system service is responsible for coordinating recovery related @@ -871,6 +881,76 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return rebootWithLskfImpl(packageName, reason, slotSwitch); } + public static boolean isUpdatableApexSupported() { + return ApexProperties.updatable().orElse(false); + } + + // Metadata should be no more than few MB, if it's larger than 100MB something is wrong. + private static final long APEX_INFO_SIZE_LIMIT = 24 * 1024 * 100; + + private static CompressedApexInfoList getCompressedApexInfoList(String packageFile) + throws IOException { + try (ZipFile zipFile = new ZipFile(packageFile)) { + final ZipEntry entry = zipFile.getEntry("apex_info.pb"); + if (entry == null) { + return null; + } + if (entry.getSize() >= APEX_INFO_SIZE_LIMIT) { + throw new IllegalArgumentException("apex_info.pb has size " + + entry.getSize() + + " which is larger than the permitted limit" + APEX_INFO_SIZE_LIMIT); + } + if (entry.getSize() == 0) { + CompressedApexInfoList infoList = new CompressedApexInfoList(); + infoList.apexInfos = new CompressedApexInfo[0]; + return infoList; + } + Log.i(TAG, "Allocating " + entry.getSize() + + " bytes of memory to store OTA Metadata"); + byte[] data = new byte[(int) entry.getSize()]; + + try (InputStream is = zipFile.getInputStream(entry)) { + int bytesRead = is.read(data); + String msg = "Read " + bytesRead + " when expecting " + data.length; + Log.e(TAG, msg); + if (bytesRead != data.length) { + throw new IOException(msg); + } + } + ApexMetadata metadata = ApexMetadata.parseFrom(data); + CompressedApexInfoList apexInfoList = new CompressedApexInfoList(); + apexInfoList.apexInfos = + Arrays.stream(metadata.apexInfo).filter(apex -> apex.isCompressed).map(apex -> { + CompressedApexInfo info = new CompressedApexInfo(); + info.moduleName = apex.packageName; + info.decompressedSize = apex.decompressedSize; + info.versionCode = apex.version; + return info; + }).toArray(CompressedApexInfo[]::new); + return apexInfoList; + } + } + + @Override + public boolean allocateSpaceForUpdate(String packageFile) { + if (!isUpdatableApexSupported()) { + Log.i(TAG, "Updatable Apex not supported, " + + "allocateSpaceForUpdate does nothing."); + return true; + } + try { + CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile); + ApexManager apexManager = ApexManager.getInstance(); + apexManager.reserveSpaceForCompressedApex(apexInfoList); + return true; + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (IOException | UnsupportedOperationException e) { + Slog.e(TAG, "Failed to reserve space for compressed apex: ", e); + } + return false; + } + @Override // Binder call public boolean isLskfCaptured(String packageName) { enforcePermissionForResumeOnReboot(); |