DSU service: Log insufficient storage space error

Log a specialized error message if installation task
failed due to insufficient storage space.
This helps the user to disgnose the source of error.

Bug: 200002443
Test: start DSU task and check logcat
Change-Id: Iabb3e0325ae99c343978ca6c35ab8378f20e0527
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index e8e4785..9610b16 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -16,13 +16,16 @@
 
 package android.os.image;
 
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.gsi.AvbPublicKey;
 import android.gsi.GsiProgress;
+import android.gsi.IGsiService;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.util.Pair;
 
 /**
  * The DynamicSystemManager offers a mechanism to use a new system image temporarily. After the
@@ -138,17 +141,18 @@
      * @param name The DSU partition name
      * @param size Size of the DSU image in bytes
      * @param readOnly True if the partition is read only, e.g. system.
-     * @return {@code true} if the call succeeds. {@code false} either the device does not contain
-     *     enough space or a DynamicSystem is currently in use where the {@link #isInUse} would be
-     *     true.
+     * @return {@code Integer} an IGsiService.INSTALL_* status code. {@link Session} an installation
+     *     session object if successful, otherwise {@code null}.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
-    public Session createPartition(String name, long size, boolean readOnly) {
+    public @NonNull Pair<Integer, Session> createPartition(
+            String name, long size, boolean readOnly) {
         try {
-            if (mService.createPartition(name, size, readOnly)) {
-                return new Session();
+            int status = mService.createPartition(name, size, readOnly);
+            if (status == IGsiService.INSTALL_OK) {
+                return new Pair<>(status, new Session());
             } else {
-                return null;
+                return new Pair<>(status, null);
             }
         } catch (RemoteException e) {
             throw new RuntimeException(e.toString());
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 4e69952..755368a 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -35,10 +35,10 @@
      * @param name The DSU partition name
      * @param size Size of the DSU image in bytes
      * @param readOnly True if this partition is readOnly
-     * @return true if the call succeeds
+     * @return IGsiService.INSTALL_* status code
      */
     @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
-    boolean createPartition(@utf8InCpp String name, long size, boolean readOnly);
+    int createPartition(@utf8InCpp String name, long size, boolean readOnly);
 
     /**
      * Complete the current partition installation.
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 3a35659..5562684 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -110,6 +110,7 @@
     private static final int EVENT_DSU_PROGRESS_UPDATE = 120000;
     private static final int EVENT_DSU_INSTALL_COMPLETE = 120001;
     private static final int EVENT_DSU_INSTALL_FAILED = 120002;
+    private static final int EVENT_DSU_INSTALL_INSUFFICIENT_SPACE = 120003;
 
     protected static void logEventProgressUpdate(
             String partitionName,
@@ -136,6 +137,10 @@
         EventLog.writeEvent(EVENT_DSU_INSTALL_FAILED, cause);
     }
 
+    protected static void logEventInsufficientSpace() {
+        EventLog.writeEvent(EVENT_DSU_INSTALL_INSUFFICIENT_SPACE);
+    }
+
     /*
      * IPC
      */
@@ -258,6 +263,8 @@
 
         if (result == RESULT_CANCELLED) {
             logEventFailed("Dynamic System installation task is canceled by the user.");
+        } else if (detail instanceof InstallationAsyncTask.InsufficientSpaceException) {
+            logEventInsufficientSpace();
         } else {
             logEventFailed("error: " + detail);
         }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags
index 4073143..8d8be10 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/EventLogTags.logtags
@@ -5,3 +5,4 @@
 120000 dsu_progress_update (partition_name|3),(installed_bytes|2|5),(total_bytes|2|5),(partition_number|1|5),(total_partition_number|1|5),(total_progress_percentage|1|5)
 120001 dsu_install_complete
 120002 dsu_install_failed (cause|3)
+120003 dsu_install_insufficient_space
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 38d851e..62e53d6 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.gsi.AvbPublicKey;
+import android.gsi.IGsiService;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -27,6 +28,7 @@
 import android.os.image.DynamicSystemManager;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Range;
 import android.webkit.URLUtil;
 
@@ -106,8 +108,15 @@
         }
     }
 
+    static class InsufficientSpaceException extends IOException {
+        InsufficientSpaceException(String message) {
+            super(message);
+        }
+    }
+
     /** UNSET means the installation is not completed */
     static final int RESULT_UNSET = 0;
+
     static final int RESULT_OK = 1;
     static final int RESULT_CANCELLED = 2;
     static final int RESULT_ERROR_IO = 3;
@@ -157,6 +166,7 @@
     private final boolean mIsNetworkUrl;
     private final boolean mIsDeviceBootloaderUnlocked;
     private final boolean mWantScratchPartition;
+    private int mCreatePartitionStatus;
     private DynamicSystemManager.Session mInstallationSession;
     private KeyRevocationList mKeyRevocationList;
 
@@ -364,7 +374,7 @@
             mIsZip = true;
         } else {
             throw new UnsupportedFormatException(
-                String.format(Locale.US, "Unsupported file format: %s", mUrl));
+                    String.format(Locale.US, "Unsupported file format: %s", mUrl));
         }
 
         if (mIsNetworkUrl) {
@@ -435,14 +445,19 @@
             throws IOException {
         Log.d(TAG, "Creating writable partition: " + partitionName + ", size: " + partitionSize);
 
-        Thread thread = new Thread() {
-            @Override
-            public void run() {
-                mInstallationSession =
-                        mDynSystem.createPartition(
-                                partitionName, partitionSize, /* readOnly= */ false);
-            }
-        };
+        mCreatePartitionStatus = 0;
+        mInstallationSession = null;
+        Thread thread =
+                new Thread() {
+                    @Override
+                    public void run() {
+                        Pair<Integer, DynamicSystemManager.Session> result =
+                                mDynSystem.createPartition(
+                                        partitionName, partitionSize, /* readOnly = */ false);
+                        mCreatePartitionStatus = result.first;
+                        mInstallationSession = result.second;
+                    }
+                };
 
         initPartitionProgress(partitionName, partitionSize, /* readonly = */ false);
         publishProgress(/* installedSize = */ 0L);
@@ -468,13 +483,17 @@
             }
         }
 
-        if (prevInstalledSize != partitionSize) {
-            publishProgress(partitionSize);
-        }
-
         if (mInstallationSession == null) {
-            throw new IOException(
-                    "Failed to start installation with requested size: " + partitionSize);
+            if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE
+                    || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) {
+                throw new InsufficientSpaceException(
+                        "Failed to create "
+                                + partitionName
+                                + " partition: storage media has insufficient free space");
+            } else {
+                throw new IOException(
+                        "Failed to start installation with requested size: " + partitionSize);
+            }
         }
 
         // Reset installation session and verify that installation completes successfully.
@@ -482,6 +501,11 @@
         if (!mDynSystem.closePartition()) {
             throw new IOException("Failed to complete partition installation: " + partitionName);
         }
+
+        // Ensure a 100% mark is published.
+        if (prevInstalledSize != partitionSize) {
+            publishProgress(partitionSize);
+        }
     }
 
     private void installScratch() throws IOException {
@@ -606,10 +630,19 @@
             throw new IOException("Cannot get raw size for " + partitionName);
         }
 
-        Thread thread = new Thread(() -> {
-            mInstallationSession =
-                    mDynSystem.createPartition(partitionName, partitionSize, true);
-        });
+        mCreatePartitionStatus = 0;
+        mInstallationSession = null;
+        Thread thread =
+                new Thread() {
+                    @Override
+                    public void run() {
+                        Pair<Integer, DynamicSystemManager.Session> result =
+                                mDynSystem.createPartition(
+                                        partitionName, partitionSize, /* readOnly = */ true);
+                        mCreatePartitionStatus = result.first;
+                        mInstallationSession = result.second;
+                    }
+                };
 
         Log.d(TAG, "Start creating partition: " + partitionName);
         thread.start();
@@ -627,8 +660,16 @@
         }
 
         if (mInstallationSession == null) {
-            throw new IOException(
-                    "Failed to start installation with requested size: " + partitionSize);
+            if (mCreatePartitionStatus == IGsiService.INSTALL_ERROR_NO_SPACE
+                    || mCreatePartitionStatus == IGsiService.INSTALL_ERROR_FILE_SYSTEM_CLUTTERED) {
+                throw new InsufficientSpaceException(
+                        "Failed to create "
+                                + partitionName
+                                + " partition: storage media has insufficient free space");
+            } else {
+                throw new IOException(
+                        "Failed to start installation with requested size: " + partitionSize);
+            }
         }
 
         Log.d(TAG, "Start installing: " + partitionName);
@@ -688,11 +729,6 @@
             installedSize += numBytesRead;
         }
 
-        // Ensure a 100% mark is published.
-        if (prevInstalledSize != partitionSize) {
-            publishProgress(partitionSize);
-        }
-
         AvbPublicKey avbPublicKey = new AvbPublicKey();
         if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) {
             imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed"));
@@ -708,6 +744,11 @@
         if (!mDynSystem.closePartition()) {
             throw new IOException("Failed to complete partition installation: " + partitionName);
         }
+
+        // Ensure a 100% mark is published.
+        if (prevInstalledSize != partitionSize) {
+            publishProgress(partitionSize);
+        }
     }
 
     private static String toHexString(byte[] bytes) {
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index e924012..99e12a8 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -119,14 +119,13 @@
 
     @Override
     @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
-    public boolean createPartition(String name, long size, boolean readOnly)
-            throws RemoteException {
+    public int createPartition(String name, long size, boolean readOnly) throws RemoteException {
         IGsiService service = getGsiService();
-        if (service.createPartition(name, size, readOnly) != 0) {
-            Slog.i(TAG, "Failed to install " + name);
-            return false;
+        int status = service.createPartition(name, size, readOnly);
+        if (status != IGsiService.INSTALL_OK) {
+            Slog.i(TAG, "Failed to create partition: " + name);
         }
-        return true;
+        return status;
     }
 
     @Override