Fix issue #3130426: Finsky crash in switching from window carousel

Need to note that we no longer have saved state before delivering
results or new intents to an activity.

Also do some work on loaders to prevent apps from making fragment
changes as a result of receiving loader data.  This makes apps
consistent crash in a case that they would previously sometimes
crash (if they got the loader data after onPause).

Change-Id: I46e9e46d0aa05d9d7d6a275a2a488a18a20a5747
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 28abcaa..4d4ea9a 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -40,7 +40,12 @@
         public Loader<D> onCreateLoader(int id, Bundle args);
 
         /**
-         * Called when a previously created loader has finished its load.
+         * Called when a previously created loader has finished its load.  Note
+         * that normally an application is <em>not</em> allowed to commit fragment
+         * transactions while in this call, since it can happen after an
+         * activity's state is saved.  See {@link FragmentManager#openTransaction()
+         * FragmentManager.openTransaction()} for further discussion on this.
+         * 
          * @param loader The Loader that has finished.
          * @param data The data generated by the Loader.
          */
@@ -102,6 +107,7 @@
     // previously run loader until the new loader's data is available.
     final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
 
+    Activity mActivity;
     boolean mStarted;
     boolean mRetaining;
     boolean mRetainingStarted;
@@ -172,12 +178,12 @@
                         stop();
                     }
                 }
-                if (mStarted && mData != null && mCallbacks != null) {
+                if (mStarted && mData != null) {
                     // This loader was retained, and now at the point of
                     // finishing the retain we find we remain started, have
                     // our data, and the owner has a new callback...  so
                     // let's deliver the data now.
-                    mCallbacks.onLoadFinished(mLoader, mData);
+                    callOnLoadFinished(mLoader, mData);
                 }
             }
         }
@@ -219,9 +225,7 @@
             // Notify of the new data so the app can switch out the old data before
             // we try to destroy it.
             mData = data;
-            if (mCallbacks != null) {
-                mCallbacks.onLoadFinished(loader, data);
-            }
+            callOnLoadFinished(loader, data);
 
             if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
 
@@ -236,6 +240,23 @@
             }
         }
 
+        void callOnLoadFinished(Loader<Object> loader, Object data) {
+            if (mCallbacks != null) {
+                String lastBecause = null;
+                if (mActivity != null) {
+                    lastBecause = mActivity.mFragments.mNoTransactionsBecause;
+                    mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished";
+                }
+                try {
+                    mCallbacks.onLoadFinished(loader, data);
+                } finally {
+                    if (mActivity != null) {
+                        mActivity.mFragments.mNoTransactionsBecause = lastBecause;
+                    }
+                }
+            }
+        }
+        
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder(64);
@@ -252,10 +273,15 @@
         }
     }
     
-    LoaderManagerImpl(boolean started) {
+    LoaderManagerImpl(Activity activity, boolean started) {
+        mActivity = activity;
         mStarted = started;
     }
     
+    void updateActivity(Activity activity) {
+        mActivity = activity;
+    }
+    
     private LoaderInfo createLoader(int id, Bundle args,
             LoaderManager.LoaderCallbacks<Object> callback) {
         LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
@@ -286,7 +312,7 @@
         
         if (info.mData != null && mStarted) {
             // If the loader has already generated its data, report it now.
-            info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
+            info.callOnLoadFinished(info.mLoader, info.mData);
         }
         
         return (Loader<D>)info.mLoader;
@@ -348,7 +374,13 @@
  
     void doStart() {
         if (DEBUG) Log.v(TAG, "Starting: " + this);
-
+        if (mStarted) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Log.w(TAG, "Called doStart when already started: " + this, e);
+            return;
+        }
+        
         // Call out to sub classes so they can start their loaders
         // Let the existing loaders know that we want to be notified when a load is complete
         for (int i = mLoaders.size()-1; i >= 0; i--) {
@@ -359,6 +391,12 @@
     
     void doStop() {
         if (DEBUG) Log.v(TAG, "Stopping: " + this);
+        if (!mStarted) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Log.w(TAG, "Called doStop when not started: " + this, e);
+            return;
+        }
 
         for (int i = mLoaders.size()-1; i >= 0; i--) {
             mLoaders.valueAt(i).stop();
@@ -368,6 +406,12 @@
     
     void doRetain() {
         if (DEBUG) Log.v(TAG, "Retaining: " + this);
+        if (!mStarted) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Log.w(TAG, "Called doRetain when not started: " + this, e);
+            return;
+        }
 
         mRetaining = true;
         mStarted = false;