Merge "Prefetch column names in bulk cursor adaptor."
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index eb83dbc..4b31552 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.content.res.AssetFileDescriptor;
+import android.database.BulkCursorDescriptor;
 import android.database.BulkCursorNative;
 import android.database.BulkCursorToCursorAdaptor;
 import android.database.Cursor;
@@ -113,20 +114,14 @@
                     if (cursor != null) {
                         CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
                                 cursor, observer, getProviderName());
-                        final IBinder binder = adaptor.asBinder();
-                        final int count = adaptor.count();
-                        final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex(
-                                adaptor.getColumnNames());
-                        final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls();
+                        BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
 
                         reply.writeNoException();
-                        reply.writeStrongBinder(binder);
-                        reply.writeInt(count);
-                        reply.writeInt(index);
-                        reply.writeInt(wantsAllOnMoveCalls ? 1 : 0);
+                        reply.writeInt(1);
+                        d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     } else {
                         reply.writeNoException();
-                        reply.writeStrongBinder(null);
+                        reply.writeInt(0);
                     }
 
                     return true;
@@ -369,12 +364,9 @@
 
             DatabaseUtils.readExceptionFromParcel(reply);
 
-            IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder());
-            if (bulkCursor != null) {
-                int rowCount = reply.readInt();
-                int idColumnPosition = reply.readInt();
-                boolean wantsAllOnMoveCalls = reply.readInt() != 0;
-                adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls);
+            if (reply.readInt() != 0) {
+                BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
+                adaptor.initialize(d);
             } else {
                 adaptor.close();
                 adaptor = null;
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b28ed8d..dd6692c 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -33,10 +33,39 @@
 public abstract class AbstractCursor implements CrossProcessCursor {
     private static final String TAG = "Cursor";
 
-    DataSetObservable mDataSetObservable = new DataSetObservable();
-    ContentObservable mContentObservable = new ContentObservable();
+    /**
+     * @deprecated This is never updated by this class and should not be used
+     */
+    @Deprecated
+    protected HashMap<Long, Map<String, Object>> mUpdatedRows;
 
-    Bundle mExtras = Bundle.EMPTY;
+    protected int mPos;
+
+    /**
+     * This must be set to the index of the row ID column by any
+     * subclass that wishes to support updates.
+     */
+    protected int mRowIdColumnIndex;
+
+    /**
+     * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
+     * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
+     * pointing at.
+     */
+    protected Long mCurrentRowID;
+
+    protected boolean mClosed;
+    protected ContentResolver mContentResolver;
+    private Uri mNotifyUri;
+
+    private final Object mSelfObserverLock = new Object();
+    private ContentObserver mSelfObserver;
+    private boolean mSelfObserverRegistered;
+
+    private DataSetObservable mDataSetObservable = new DataSetObservable();
+    private ContentObservable mContentObservable = new ContentObservable();
+
+    private Bundle mExtras = Bundle.EMPTY;
 
     /* -------------------------------------------------------- */
     /* These need to be implemented by subclasses */
@@ -415,30 +444,4 @@
             }
         }
     }
-
-    /**
-     * @deprecated This is never updated by this class and should not be used
-     */
-    @Deprecated
-    protected HashMap<Long, Map<String, Object>> mUpdatedRows;
-
-    /**
-     * This must be set to the index of the row ID column by any
-     * subclass that wishes to support updates.
-     */
-    protected int mRowIdColumnIndex;
-
-    protected int mPos;
-    /**
-     * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
-     * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
-     * pointing at.
-     */
-    protected Long mCurrentRowID;
-    protected ContentResolver mContentResolver;
-    protected boolean mClosed = false;
-    private Uri mNotifyUri;
-    private ContentObserver mSelfObserver;
-    final private Object mSelfObserverLock = new Object();
-    private boolean mSelfObserverRegistered;
 }
diff --git a/core/java/android/database/BulkCursorDescriptor.java b/core/java/android/database/BulkCursorDescriptor.java
new file mode 100644
index 0000000..c1e5e63
--- /dev/null
+++ b/core/java/android/database/BulkCursorDescriptor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the properties of a {@link CursorToBulkCursorAdaptor} that are
+ * needed to initialize its {@link BulkCursorToCursorAdaptor} counterpart on the client's end.
+ *
+ * {@hide}
+ */
+public final class BulkCursorDescriptor implements Parcelable {
+    public static final Parcelable.Creator<BulkCursorDescriptor> CREATOR =
+            new Parcelable.Creator<BulkCursorDescriptor>() {
+        @Override
+        public BulkCursorDescriptor createFromParcel(Parcel in) {
+            BulkCursorDescriptor d = new BulkCursorDescriptor();
+            d.readFromParcel(in);
+            return d;
+        }
+
+        @Override
+        public BulkCursorDescriptor[] newArray(int size) {
+            return new BulkCursorDescriptor[size];
+        }
+    };
+
+    public IBulkCursor cursor;
+    public String[] columnNames;
+    public boolean wantsAllOnMoveCalls;
+    public int count;
+    public CursorWindow window;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(cursor.asBinder());
+        out.writeStringArray(columnNames);
+        out.writeInt(wantsAllOnMoveCalls ? 1 : 0);
+        out.writeInt(count);
+        if (window != null) {
+            out.writeInt(1);
+            window.writeToParcel(out, flags);
+        } else {
+            out.writeInt(0);
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        cursor = BulkCursorNative.asInterface(in.readStrongBinder());
+        columnNames = in.readStringArray();
+        wantsAllOnMoveCalls = in.readInt() != 0;
+        count = in.readInt();
+        if (in.readInt() != 0) {
+            window = CursorWindow.CREATOR.createFromParcel(in);
+        }
+    }
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 67cf0f8..d3c11e7 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -72,26 +72,6 @@
                     return true;
                 }
 
-                case COUNT_TRANSACTION: {
-                    data.enforceInterface(IBulkCursor.descriptor);
-                    int count = count();
-                    reply.writeNoException();
-                    reply.writeInt(count);
-                    return true;
-                }
-
-                case GET_COLUMN_NAMES_TRANSACTION: {
-                    data.enforceInterface(IBulkCursor.descriptor);
-                    String[] columnNames = getColumnNames();
-                    reply.writeNoException();
-                    reply.writeInt(columnNames.length);
-                    int length = columnNames.length;
-                    for (int i = 0; i < length; i++) {
-                        reply.writeString(columnNames[i]);
-                    }
-                    return true;
-                }
-
                 case DEACTIVATE_TRANSACTION: {
                     data.enforceInterface(IBulkCursor.descriptor);
                     deactivate();
@@ -125,14 +105,6 @@
                     return true;
                 }
 
-                case WANTS_ON_MOVE_TRANSACTION: {
-                    data.enforceInterface(IBulkCursor.descriptor);
-                    boolean result = getWantsAllOnMoveCalls();
-                    reply.writeNoException();
-                    reply.writeInt(result ? 1 : 0);
-                    return true;
-                }
-
                 case GET_EXTRAS_TRANSACTION: {
                     data.enforceInterface(IBulkCursor.descriptor);
                     Bundle extras = getExtras();
@@ -217,52 +189,6 @@
         }
     }
 
-    public int count() throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        try {
-            data.writeInterfaceToken(IBulkCursor.descriptor);
-
-            boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
-            DatabaseUtils.readExceptionFromParcel(reply);
-
-            int count;
-            if (result == false) {
-                count = -1;
-            } else {
-                count = reply.readInt();
-            }
-            return count;
-        } finally {
-            data.recycle();
-            reply.recycle();
-        }
-    }
-
-    public String[] getColumnNames() throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        try {
-            data.writeInterfaceToken(IBulkCursor.descriptor);
-
-            mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
-            DatabaseUtils.readExceptionFromParcel(reply);
-
-            String[] columnNames = null;
-            int numColumns = reply.readInt();
-            columnNames = new String[numColumns];
-            for (int i = 0; i < numColumns; i++) {
-                columnNames[i] = reply.readString();
-            }
-            return columnNames;
-        } finally {
-            data.recycle();
-            reply.recycle();
-        }
-    }
-
     public void deactivate() throws RemoteException
     {
         Parcel data = Parcel.obtain();
@@ -317,23 +243,6 @@
         }
     }
 
-    public boolean getWantsAllOnMoveCalls() throws RemoteException {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        try {
-            data.writeInterfaceToken(IBulkCursor.descriptor);
-
-            mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
-            DatabaseUtils.readExceptionFromParcel(reply);
-
-            int result = reply.readInt();
-            return result != 0;
-        } finally {
-            data.recycle();
-            reply.recycle();
-        }
-    }
-
     public Bundle getExtras() throws RemoteException {
         if (mExtras == null) {
             Parcel data = Parcel.obtain();
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 885046b..98c7043 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -30,34 +30,23 @@
 
     private SelfContentObserver mObserverBridge = new SelfContentObserver(this);
     private IBulkCursor mBulkCursor;
-    private int mCount;
     private String[] mColumns;
     private boolean mWantsAllOnMoveCalls;
+    private int mCount;
 
     /**
      * Initializes the adaptor.
      * Must be called before first use.
      */
-    public void initialize(IBulkCursor bulkCursor, int count, int idIndex,
-            boolean wantsAllOnMoveCalls) {
-        mBulkCursor = bulkCursor;
-        mColumns = null;  // lazily retrieved
-        mCount = count;
-        mRowIdColumnIndex = idIndex;
-        mWantsAllOnMoveCalls = wantsAllOnMoveCalls;
-    }
-
-    /**
-     * Returns column index of "_id" column, or -1 if not found.
-     */
-    public static int findRowIdColumnIndex(String[] columnNames) {
-        int length = columnNames.length;
-        for (int i = 0; i < length; i++) {
-            if (columnNames[i].equals("_id")) {
-                return i;
-            }
+    public void initialize(BulkCursorDescriptor d) {
+        mBulkCursor = d.cursor;
+        mColumns = d.columnNames;
+        mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
+        mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
+        mCount = d.count;
+        if (d.window != null) {
+            setWindow(d.window);
         }
-        return -1;
     }
 
     /**
@@ -169,14 +158,6 @@
     public String[] getColumnNames() {
         throwIfCursorIsClosed();
 
-        if (mColumns == null) {
-            try {
-                mColumns = mBulkCursor.getColumnNames();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to fetch column names because the remote process is dead");
-                return null;
-            }
-        }
         return mColumns;
     }
 
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 167278a..525be96 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -132,6 +132,25 @@
         }
     }
 
+    public BulkCursorDescriptor getBulkCursorDescriptor() {
+        synchronized (mLock) {
+            throwIfCursorIsClosed();
+
+            BulkCursorDescriptor d = new BulkCursorDescriptor();
+            d.cursor = this;
+            d.columnNames = mCursor.getColumnNames();
+            d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
+            d.count = mCursor.getCount();
+            d.window = mCursor.getWindow();
+            if (d.window != null) {
+                // Acquire a reference to the window because its reference count will be
+                // decremented when it is returned as part of the binder call reply parcel.
+                d.window.acquireReference();
+            }
+            return d;
+        }
+    }
+
     @Override
     public CursorWindow getWindow(int position) {
         synchronized (mLock) {
@@ -157,10 +176,9 @@
                 mCursor.fillWindow(position, window);
             }
 
-            // Acquire a reference before returning from this RPC.
-            // The Binder proxy will decrement the reference count again as part of writing
-            // the CursorWindow to the reply parcel as a return value.
             if (window != null) {
+                // Acquire a reference to the window because its reference count will be
+                // decremented when it is returned as part of the binder call reply parcel.
                 window.acquireReference();
             }
             return window;
@@ -177,24 +195,6 @@
     }
 
     @Override
-    public int count() {
-        synchronized (mLock) {
-            throwIfCursorIsClosed();
-
-            return mCursor.getCount();
-        }
-    }
-
-    @Override
-    public String[] getColumnNames() {
-        synchronized (mLock) {
-            throwIfCursorIsClosed();
-
-            return mCursor.getColumnNames();
-        }
-    }
-
-    @Override
     public void deactivate() {
         synchronized (mLock) {
             if (mCursor != null) {
@@ -237,15 +237,6 @@
         }
     }
 
-    @Override
-    public boolean getWantsAllOnMoveCalls() {
-        synchronized (mLock) {
-            throwIfCursorIsClosed();
-
-            return mCursor.getWantsAllOnMoveCalls();
-        }
-    }
-
     /**
      * Create a ContentObserver from the observer and register it as an observer on the
      * underlying cursor.
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 99d260e..40a54cf 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -1386,4 +1386,18 @@
         System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
         return result;
     }
+
+    /**
+     * Returns column index of "_id" column, or -1 if not found.
+     * @hide
+     */
+    public static int findRowIdColumnIndex(String[] columnNames) {
+        int length = columnNames.length;
+        for (int i = 0; i < length; i++) {
+            if (columnNames[i].equals("_id")) {
+                return i;
+            }
+        }
+        return -1;
+    }
 }
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
index 0f4500a..b551116 100644
--- a/core/java/android/database/IBulkCursor.java
+++ b/core/java/android/database/IBulkCursor.java
@@ -43,29 +43,12 @@
      */
     public void onMove(int position) throws RemoteException;
 
-    /**
-     * Returns the number of rows in the cursor.
-     *
-     * @return the number of rows in the cursor.
-     */
-    public int count() throws RemoteException;
-
-    /**
-     * Returns a string array holding the names of all of the columns in the
-     * cursor in the order in which they were listed in the result.
-     *
-     * @return the names of the columns returned in this query.
-     */
-    public String[] getColumnNames() throws RemoteException;
-
     public void deactivate() throws RemoteException;
 
     public void close() throws RemoteException;
 
     public int requery(IContentObserver observer) throws RemoteException;
 
-    boolean getWantsAllOnMoveCalls() throws RemoteException;
-
     Bundle getExtras() throws RemoteException;
 
     Bundle respond(Bundle extras) throws RemoteException;
@@ -74,13 +57,10 @@
     static final String descriptor = "android.content.IBulkCursor";
 
     static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
-    static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
-    static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
-    static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
-    static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
-    static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7;
-    static final int WANTS_ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 8;
-    static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
-    static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
-    static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
+    static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+    static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+    static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+    static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
+    static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
+    static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
 }
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 82bb23e..b29897e 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -105,12 +105,7 @@
         mQuery = query;
 
         mColumns = query.getColumnNames();
-        for (int i = 0; i < mColumns.length; i++) {
-            // Make note of the row ID column index for quick access to it
-            if ("_id".equals(mColumns[i])) {
-                mRowIdColumnIndex = i;
-            }
-        }
+        mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
     }
 
     /**