Fix issue #3381489: IllegalStateException: attempt to re-open...

...an already-closed object: android.database.sqlite.SQLiteQuery

It turns out there is a state we are missing -- the loader is
still needed, but in the inactive list.  In this case the loader
needs to continue holding on to its current data, and not deliver
any new data (which would result in it releasing its old data).

This introduces the new state to Loader, and uses it in
AsyncTaskLoader so all subclasses of that should get the new
correct behavior.

A further improvement would be to unregister CursorLoader's
content listener when going in to this state, but that can
wait for later.

Change-Id: I6d30173b94f8e30b5be31d018accd328cc3388ec
diff --git a/api/11.xml b/api/11.xml
index 5d42845..16984b5 100644
--- a/api/11.xml
+++ b/api/11.xml
@@ -43763,19 +43763,6 @@
 <parameter name="data" type="D">
 </parameter>
 </method>
-<method name="onCancelled"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="data" type="D">
-</parameter>
-</method>
 <method name="onLoadInBackground"
  return="D"
  abstract="false"
@@ -55609,6 +55596,17 @@
 <parameter name="context" type="android.content.Context">
 </parameter>
 </constructor>
+<method name="abandon"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="dataToString"
  return="java.lang.String"
  abstract="false"
@@ -55687,6 +55685,17 @@
  visibility="public"
 >
 </method>
+<method name="isAbandoned"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isReset"
  return="boolean"
  abstract="false"
@@ -55709,6 +55718,17 @@
  visibility="public"
 >
 </method>
+<method name="onAbandon"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
 <method name="onContentChanged"
  return="void"
  abstract="false"
diff --git a/api/current.xml b/api/current.xml
index 5d42845..16984b5 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -43763,19 +43763,6 @@
 <parameter name="data" type="D">
 </parameter>
 </method>
-<method name="onCancelled"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="data" type="D">
-</parameter>
-</method>
 <method name="onLoadInBackground"
  return="D"
  abstract="false"
@@ -55609,6 +55596,17 @@
 <parameter name="context" type="android.content.Context">
 </parameter>
 </constructor>
+<method name="abandon"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="dataToString"
  return="java.lang.String"
  abstract="false"
@@ -55687,6 +55685,17 @@
  visibility="public"
 >
 </method>
+<method name="isAbandoned"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isReset"
  return="boolean"
  abstract="false"
@@ -55709,6 +55718,17 @@
  visibility="public"
 >
 </method>
+<method name="onAbandon"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
 <method name="onContentChanged"
  return="void"
  abstract="false"
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 431be05..1ee386d 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -587,6 +587,7 @@
                     if (DEBUG) Log.v(TAG, "  Removing last inactive loader: " + info);
                     inactive.mDeliveredData = false;
                     inactive.destroy();
+                    info.mLoader.abandon();
                     mInactiveLoaders.put(id, info);
                 } else {
                     // We already have an inactive loader for this ID that we are
@@ -617,6 +618,7 @@
                 // Keep track of the previous instance of this loader so we can destroy
                 // it when the new one completes.
                 if (DEBUG) Log.v(TAG, "  Making last loader inactive: " + info);
+                info.mLoader.abandon();
                 mInactiveLoaders.put(id, info);
             }
         }
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index a7dd5fb..383cb6b 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -169,11 +169,6 @@
      * to properly dispose of the result.
      */
     public void onCanceled(D data) {
-        onCancelled(data);
-    }
-
-    @Deprecated
-    public void onCancelled(D data) {
     }
 
     void executePendingTask() {
@@ -214,10 +209,15 @@
             if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
             dispatchOnCancelled(task, data);
         } else {
-            mLastLoadCompleteTime = SystemClock.uptimeMillis();
-            mTask = null;
-            if (DEBUG) Slog.v(TAG, "Delivering result");
-            deliverResult(data);
+            if (isAbandoned()) {
+                // This cursor has been abandoned; just cancel the new data.
+                onCanceled(data);
+            } else {
+                mLastLoadCompleteTime = SystemClock.uptimeMillis();
+                mTask = null;
+                if (DEBUG) Slog.v(TAG, "Delivering result");
+                deliverResult(data);
+            }
         }
     }
 
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index d63fe69..a9d6117 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -45,6 +45,7 @@
     OnLoadCompleteListener<D> mListener;
     Context mContext;
     boolean mStarted = false;
+    boolean mAbandoned = false;
     boolean mReset = true;
     boolean mContentChanged = false;
 
@@ -151,6 +152,15 @@
     }
 
     /**
+     * Return whether this loader has been abandoned.  In this state, the
+     * loader <em>must not</em> report any new data, and <em>must</em> keep
+     * its last reported data valid until it is finally reset.
+     */
+    public boolean isAbandoned() {
+        return mAbandoned;
+    }
+
+    /**
      * Return whether this load has been reset.  That is, either the loader
      * has not yet been started for the first time, or its {@link #reset()}
      * has been called.
@@ -177,6 +187,7 @@
     public final void startLoading() {
         mStarted = true;
         mReset = false;
+        mAbandoned = false;
         onStartLoading();
     }
 
@@ -236,6 +247,28 @@
     }
 
     /**
+     * Tell the Loader that it is being abandoned.  This is called prior
+     * to {@link #reset} to have it retain its current data but not report
+     * any new data.
+     */
+    public void abandon() {
+        mAbandoned = true;
+        onAbandon();
+    }
+    
+    /**
+     * Subclasses implement this to take care of being abandoned.  This is
+     * an optional intermediate state prior to {@link #onReset()} -- it means that
+     * the client is no longer interested in any new data from the loader,
+     * so the loader must not report any further updates.  However, the
+     * loader <em>must</em> keep its last reported data valid until the final
+     * {@link #onReset()} happens.  You can retrieve the current abandoned
+     * state with {@link #isAbandoned}.
+     */
+    protected void onAbandon() {        
+    }
+    
+    /**
      * Resets the state of the Loader.  The Loader should at this point free
      * all of its resources, since it may never be called again; however, its
      * {@link #startLoading()} may later be called at which point it must be
@@ -251,6 +284,7 @@
         onReset();
         mReset = true;
         mStarted = false;
+        mAbandoned = false;
         mContentChanged = false;
     }
 
@@ -327,6 +361,7 @@
                 writer.print(" mListener="); writer.println(mListener);
         writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
                 writer.print(" mContentChanged="); writer.print(mContentChanged);
+                writer.print(" mAbandoned="); writer.print(mAbandoned);
                 writer.print(" mReset="); writer.println(mReset);
     }
 }
\ No newline at end of file