Propagate original exception when throwing load exception

Bug 5198078

Change-Id: I941dc9b96d6c58eb1912d446c3c27ec4c4c2ffef
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 1107530..a28b1db 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -93,14 +93,7 @@
         /**
          * Singleton instance that represents "No Contact Found"
          */
-        public static final Result NOT_FOUND = new Result();
-
-        /**
-         * Singleton instance that represents an error, e.g. because of an invalid Uri
-         * TODO: We should come up with something nicer here. Maybe use an Either type so
-         * that we can capture the Exception?
-         */
-        public static final Result ERROR = new Result();
+        public static final Result NOT_FOUND = new Result((Exception) null);
 
         private final Uri mLookupUri;
         private final Uri mUri;
@@ -135,11 +128,12 @@
         private final String mCustomRingtone;
         private final boolean mIsUserProfile;
 
+        private final Exception mException;
+
         /**
-         * Constructor for case "no contact found". This must only be used for the
-         * final {@link Result#NOT_FOUND} singleton
+         * Constructor for special results, namely "no contact found" and "error".
          */
-        private Result() {
+        private Result(Exception exception) {
             mLookupUri = null;
             mUri = null;
             mDirectoryId = -1;
@@ -161,7 +155,11 @@
             mSendToVoicemail = false;
             mCustomRingtone = null;
             mIsUserProfile = false;
+            mException = exception;
+        }
 
+        private static Result forError(Exception exception) {
+            return new Result(exception);
         }
 
         /**
@@ -172,6 +170,7 @@
                 String displayName, String altDisplayName, String phoneticName, boolean starred,
                 Integer presence, boolean sendToVoicemail, String customRingtone,
                 boolean isUserProfile) {
+            mException = null;
             mLookupUri = lookupUri;
             mUri = uri;
             mDirectoryId = directoryId;
@@ -196,6 +195,7 @@
         }
 
         private Result(Result from) {
+            mException = from.mException;
             mLookupUri = from.mLookupUri;
             mUri = from.mUri;
             mDirectoryId = from.mDirectoryId;
@@ -267,6 +267,18 @@
             return mId;
         }
 
+        /**
+         * @return true when an exception happened during loading, in which case
+         *     {@link #getException} returns the actual exception object.
+         */
+        public boolean isError() {
+            return mException != null;
+        }
+
+        public Exception getException() {
+            return mException;
+        }
+
         public long getNameRawContactId() {
             return mNameRawContactId;
         }
@@ -631,7 +643,7 @@
                 return result;
             } catch (Exception e) {
                 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
-                return Result.ERROR;
+                return Result.forError(e);
             }
         }
 
@@ -1087,7 +1099,7 @@
 
             mContact = result;
 
-            if (result != Result.ERROR && result != Result.NOT_FOUND) {
+            if (!result.isError() && result != Result.NOT_FOUND) {
                 mLookupUri = result.getLookupUri();
 
                 if (!result.isDirectoryEntry()) {
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index c82d735..f3c6158 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -187,13 +187,10 @@
                 return;
             }
 
-            if (data == ContactLoader.Result.ERROR) {
+            if (data.isError()) {
                 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
                 // should log the actual exception.
-                // TODO: Make the {@link ContactLoader.Result} pass the exception so we can include
-                // the original stack trace when this error is thrown.
-                throw new IllegalStateException("The result of the ContactLoader is "
-                        + "ContactLoader.Result.ERROR");
+                throw new IllegalStateException("Failed to load contact", data.getException());
             } else if (data == ContactLoader.Result.NOT_FOUND) {
                 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
                 mContactData = null;
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 5a96e7f..046b1ed 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -1610,7 +1610,7 @@
         public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
             final long loaderCurrentTime = SystemClock.elapsedRealtime();
             Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
-            if (data == ContactLoader.Result.NOT_FOUND || data == ContactLoader.Result.ERROR) {
+            if (data == ContactLoader.Result.NOT_FOUND || data.isError()) {
                 // Item has been deleted
                 Log.i(TAG, "No contact found. Closing activity");
                 if (mListener != null) mListener.onContactNotFound();
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index b29a9cd..cd6a494 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -133,8 +133,7 @@
         final RemoteViews views = new RemoteViews(context.getPackageName(),
                 R.layout.social_widget);
 
-        if (contactData == ContactLoader.Result.ERROR ||
-                contactData == ContactLoader.Result.NOT_FOUND) {
+        if (contactData.isError() || contactData == ContactLoader.Result.NOT_FOUND) {
             setDisplayNameAndSnippet(context, views,
                     context.getString(R.string.invalidContactMessage), null, null, null);
             setPhoto(views, ContactBadgeUtil.loadPlaceholderPhoto(context));
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
index 3560ce1..5d44cf1 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -76,17 +76,17 @@
 
     public void testNullUri() {
         ContactLoader.Result result = assertLoadContact(null);
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testEmptyUri() {
         ContactLoader.Result result = assertLoadContact(Uri.EMPTY);
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testInvalidUri() {
         ContactLoader.Result result = assertLoadContact(Uri.parse("content://wtf"));
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testLoadContactWithContactIdUri() {