Merge "Pass exception detail to API user"
diff --git a/api/system-current.txt b/api/system-current.txt
index cbe6d9e..5e1d63f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5668,7 +5668,7 @@
   }
 
   public static interface DynamicSystemClient.OnStatusChangedListener {
-    method public void onStatusChanged(int, int, long);
+    method public void onStatusChanged(int, int, long, @Nullable Throwable);
   }
 
 }
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 33a6ee8..87367ac 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
@@ -31,6 +32,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -100,9 +102,10 @@
          * @param status status code, also defined in {@code DynamicSystemClient}.
          * @param cause cause code, also defined in {@code DynamicSystemClient}.
          * @param progress number of bytes installed.
+         * @param detail additional detail about the error if available, otherwise null.
          */
         void onStatusChanged(@InstallationStatus int status, @StatusChangedCause int cause,
-                @BytesLong long progress);
+                @BytesLong long progress, @Nullable Throwable detail);
     }
 
     /*
@@ -177,6 +180,12 @@
      */
     public static final String KEY_INSTALLED_SIZE = "KEY_INSTALLED_SIZE";
 
+    /**
+     * Message key, used when the service is sending exception detail to the client.
+     * @hide
+     */
+    public static final String KEY_EXCEPTION_DETAIL = "KEY_EXCEPTION_DETAIL";
+
     /*
      * Intent Actions
      */
@@ -248,7 +257,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to get status from installation service");
                 mExecutor.execute(() -> {
-                    mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0);
+                    mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
                 });
             }
         }
@@ -396,14 +405,19 @@
                 int status = msg.arg1;
                 int cause = msg.arg2;
                 // obj is non-null
-                long progress = ((Bundle) msg.obj).getLong(KEY_INSTALLED_SIZE);
+                Bundle bundle = (Bundle) msg.obj;
+                long progress = bundle.getLong(KEY_INSTALLED_SIZE);
+                ParcelableException t = (ParcelableException) bundle.getSerializable(
+                        KEY_EXCEPTION_DETAIL);
+
+                Throwable detail = t == null ? null : t.getCause();
 
                 if (mExecutor != null) {
                     mExecutor.execute(() -> {
-                        mListener.onStatusChanged(status, cause, progress);
+                        mListener.onStatusChanged(status, cause, progress, detail);
                     });
                 } else {
-                    mListener.onStatusChanged(status, cause, progress);
+                    mListener.onStatusChanged(status, cause, progress, detail);
                 }
                 break;
             default:
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
index df2c571..e479e38 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
@@ -49,6 +49,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.ParcelableException;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.image.DynamicSystemClient;
@@ -170,13 +171,13 @@
     @Override
     public void onProgressUpdate(long installedSize) {
         mInstalledSize = installedSize;
-        postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED);
+        postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null);
     }
 
     @Override
-    public void onResult(int result) {
+    public void onResult(int result, Throwable detail) {
         if (result == RESULT_OK) {
-            postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED);
+            postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED, null);
             return;
         }
 
@@ -185,15 +186,15 @@
 
         switch (result) {
             case RESULT_ERROR_IO:
-                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO);
+                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail);
                 break;
 
             case RESULT_ERROR_INVALID_URL:
-                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL);
+                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL, detail);
                 break;
 
             case RESULT_ERROR_EXCEPTION:
-                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION);
+                postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION, detail);
                 break;
         }
     }
@@ -201,7 +202,7 @@
     @Override
     public void onCancelled() {
         resetTaskAndStop();
-        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED);
+        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
     }
 
     private void executeInstallCommand(Intent intent) {
@@ -266,7 +267,7 @@
                 Toast.LENGTH_LONG).show();
 
         resetTaskAndStop();
-        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED);
+        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
 
         mDynSystem.remove();
     }
@@ -414,7 +415,7 @@
         return VerificationActivity.isVerified(url);
     }
 
-    private void postStatus(int status, int cause) {
+    private void postStatus(int status, int cause, Throwable detail) {
         Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause);
 
         boolean notifyOnNotificationBar = true;
@@ -433,18 +434,24 @@
 
         for (int i = mClients.size() - 1; i >= 0; i--) {
             try {
-                notifyOneClient(mClients.get(i), status, cause);
+                notifyOneClient(mClients.get(i), status, cause, detail);
             } catch (RemoteException e) {
                 mClients.remove(i);
             }
         }
     }
 
-    private void notifyOneClient(Messenger client, int status, int cause) throws RemoteException {
+    private void notifyOneClient(Messenger client, int status, int cause, Throwable detail)
+            throws RemoteException {
         Bundle bundle = new Bundle();
 
         bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mInstalledSize);
 
+        if (detail != null) {
+            bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL,
+                    new ParcelableException(detail));
+        }
+
         client.send(Message.obtain(null,
                   DynamicSystemClient.MSG_POST_STATUS, status, cause, bundle));
     }
@@ -496,7 +503,7 @@
                     int status = getStatus();
 
                     // tell just registered client my status, but do not specify cause
-                    notifyOneClient(client, status, CAUSE_NOT_SPECIFIED);
+                    notifyOneClient(client, status, CAUSE_NOT_SPECIFIED, null);
 
                     mClients.add(client);
                 } catch (RemoteException e) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
index 052fc0a..aee5de5 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
@@ -31,7 +31,7 @@
 import java.util.zip.GZIPInputStream;
 
 
-class InstallationAsyncTask extends AsyncTask<String, Long, Integer> {
+class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
 
     private static final String TAG = "InstallationAsyncTask";
 
@@ -43,7 +43,6 @@
         }
     }
 
-
     /** Not completed, including being cancelled */
     static final int NO_RESULT = 0;
     static final int RESULT_OK = 1;
@@ -53,7 +52,7 @@
 
     interface InstallStatusListener {
         void onProgressUpdate(long installedSize);
-        void onResult(int resultCode);
+        void onResult(int resultCode, Throwable detail);
         void onCancelled();
     }
 
@@ -84,7 +83,7 @@
     }
 
     @Override
-    protected Integer doInBackground(String... voids) {
+    protected Throwable doInBackground(String... voids) {
         Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
 
         try {
@@ -108,7 +107,7 @@
                 if (isCancelled()) {
                     boolean aborted = mDynSystem.abort();
                     Log.d(TAG, "Called DynamicSystemManager.abort(), result = " + aborted);
-                    return RESULT_OK;
+                    return null;
                 }
 
                 GsiProgress progress = mDynSystem.getInstallationProgress();
@@ -124,10 +123,8 @@
 
 
             if (mInstallationSession == null) {
-                Log.e(TAG, "Failed to start installation with requested size: "
+                throw new IOException("Failed to start installation with requested size: "
                         + (mSystemSize + mUserdataSize));
-
-                return RESULT_ERROR_IO;
             }
 
             installedSize = mUserdataSize;
@@ -157,20 +154,11 @@
                 }
             }
 
-            return RESULT_OK;
-
-        } catch (IOException e) {
-            e.printStackTrace();
-            return RESULT_ERROR_IO;
-
-        } catch (InvalidImageUrlException e) {
-            e.printStackTrace();
-            return RESULT_ERROR_INVALID_URL;
+            return null;
 
         } catch (Exception e) {
             e.printStackTrace();
-            return RESULT_ERROR_EXCEPTION;
-
+            return e;
         } finally {
             close();
         }
@@ -180,19 +168,24 @@
     protected void onCancelled() {
         Log.d(TAG, "onCancelled(), URL: " + mUrl);
 
-        close();
-
         mListener.onCancelled();
     }
 
     @Override
-    protected void onPostExecute(Integer result) {
-        Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
+    protected void onPostExecute(Throwable detail) {
+        if (detail == null) {
+            mResult = RESULT_OK;
+        } else if (detail instanceof IOException) {
+            mResult = RESULT_ERROR_IO;
+        } else if (detail instanceof InvalidImageUrlException) {
+            mResult = RESULT_ERROR_INVALID_URL;
+        } else {
+            mResult = RESULT_ERROR_EXCEPTION;
+        }
 
-        close();
+        Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult);
 
-        mResult = result;
-        mListener.onResult(mResult);
+        mListener.onResult(mResult, detail);
     }
 
     @Override