Detect impressions, and cleanup the SearchDialog / SuggestionCursor communication.

(framework portion)

There are now 4 times the search dialog will check with the cursor:
- after data set changed
- when an item is clicked
- when the cursor is about to be closed
- when an item at a particular position is detected as showing

these are now the points where we can add data in either direction, which we use to accomplish:
- finding out whether there are any pending results
- find out if there is a position at which to notify when it is displayed (the "more results" triggering)
- toggling the "more results" button
- sending the max position that was displayed when the cursor is done

the new behavior (in addition to the refactoring) is improved detection of "more results" to trigger the additional sources
(it is now precise), and detection of which items were displayed to the user.
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index bcb2791..0bb483b 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -1202,16 +1202,7 @@
     protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
         Cursor c = mSuggestionsAdapter.getCursor();
         if ((c != null) && c.moveToPosition(position)) {
-            // let the cursor know which position was clicked
-            final Bundle clickResponse = new Bundle(1);
-            clickResponse.putInt(SearchManager.RESPOND_EXTRA_POSITION_CLICKED, position);
-            final Bundle response = c.respond(clickResponse);
-
-            // the convention is to send a position to select in response to a click (if applicable)
-            final int posToSelect = response.getInt(
-                    SearchManager.RESPOND_EXTRA_POSITION_SELECTED,
-                    SuggestionsAdapter.NO_ITEM_TO_SELECT);
-            mSuggestionsAdapter.setListItemToSelect(posToSelect);            
+            mSuggestionsAdapter.callCursorOnClick(c, position);
 
             // launch the intent
             Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 841e6aa..b0c248c5 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1167,38 +1167,50 @@
      */
     public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
 
-
     /**
-     * Used by the search dialog to ask the global search provider whether there are any pending
-     * sources that have yet to respond.  Specifically, the search dialog will call
-     * {@link Cursor#respond} with a bundle containing this extra as a key, and expect the same key
-     * to be in the response, with a boolean value indicating whether there are pending sources.
+     * Defines the constants used in the communication between {@link android.app.SearchDialog} and
+     * the global search provider via {@link Cursor#respond(android.os.Bundle)}.
      *
-     * {@hide}
+     * @hide
      */
-    public final static String RESPOND_EXTRA_PENDING_SOURCES = "respond_extra_pending_sources";
+    public static class DialogCursorProtocol {
 
-    /**
-     * Used by the search dialog to tell the cursor that supplied suggestions which item was clicked
-     * before launching the intent.  The search dialog will call {@link Cursor#respond} with a
-     * bundle containing this extra as a key and the position that was clicked as the value.
-     *
-     * The response bundle will use {@link #RESPOND_EXTRA_POSITION_SELECTED} to return an int value
-     * of the index that should be selected, if applicable.
-     *
-     * {@hide}
-     */
-    public final static String RESPOND_EXTRA_POSITION_CLICKED = "respond_extra_position_clicked";
+        /**
+         * The sent bundle will contain this integer key, with a value set to one of the events
+         * below.
+         */
+        public final static String METHOD = "DialogCursorProtocol.method";
 
-    /**
-     * Used as a key in the response bundle from a call to {@link Cursor#respond} that sends the
-     * position that is clicked.
-     *
-     * @see #RESPOND_EXTRA_POSITION_CLICKED
-     *
-     * {@hide}
-     */
-    public final static String RESPOND_EXTRA_POSITION_SELECTED = "respond_extra_position_selected";
+        /**
+         * After data has been refreshed.
+         */
+        public final static int POST_REFRESH = 0;
+        public final static String POST_REFRESH_RECEIVE_ISPENDING
+                = "DialogCursorProtocol.POST_REFRESH.isPending";
+        public final static String POST_REFRESH_RECEIVE_DISPLAY_NOTIFY
+                = "DialogCursorProtocol.POST_REFRESH.displayNotify";
+
+        /**
+         * Just before closing the cursor.
+         */
+        public final static int PRE_CLOSE = 1;
+        public final static String PRE_CLOSE_SEND_MAX_DISPLAY_POS
+                = "DialogCursorProtocol.PRE_CLOSE.sendDisplayPosition";
+
+        /**
+         * When a position has been clicked.
+         */
+        public final static int CLICK = 2;
+        public final static String CLICK_SEND_POSITION
+                = "DialogCursorProtocol.CLICK.sendPosition";
+        public final static String CLICK_RECEIVE_SELECTED_POS
+                = "DialogCursorProtocol.CLICK.receiveSelectedPosition";
+
+        /**
+         * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed.
+         */
+        public final static int THRESH_HIT = 3;
+    }
 
     /**
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 9c82429..48d8d12 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -33,6 +33,8 @@
 import android.widget.ResourceCursorAdapter;
 import android.widget.TextView;
 
+import static android.app.SearchManager.DialogCursorProtocol;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -44,15 +46,7 @@
  * @hide
  */
 class SuggestionsAdapter extends ResourceCursorAdapter {
-    // The value used to query a cursor whether it is still expecting more input,
-    // so we can correctly display (or not display) the 'working' spinner in the search dialog.
-    public static final String IS_WORKING = "isWorking";
-    
-    // The value used to tell a cursor to display the corpus selectors, if this is global
-    // search. Also returns the index of the more results item to allow the SearchDialog
-    // to tell the ListView to scroll to that list item.
-    public static final String SHOW_CORPUS_SELECTORS = "showCorpusSelectors";
-    
+
     private static final boolean DBG = false;
     private static final String LOG_TAG = "SuggestionsAdapter";
     
@@ -73,10 +67,16 @@
     // a particular list item should be selected upon the next call to notifyDataSetChanged.
     // This is used to indicate the index of the "More results..." list item so that when
     // the data set changes after a click of "More results...", we can correctly tell the
-    // ListView to scroll to the right line item. It gets reset to NO_ITEM_TO_SELECT every time it
+    // ListView to scroll to the right line item. It gets reset to NONE every time it
     // is consumed.
-    private int mListItemToSelect = NO_ITEM_TO_SELECT;
-    static final int NO_ITEM_TO_SELECT = -1;
+    private int mListItemToSelect = NONE;
+    static final int NONE = -1;
+
+    // holds the maximum position that has been displayed to the user
+    int mMaxDisplayed = NONE;
+
+    // holds the position that, when displayed, should result in notifying the cursor
+    int mDisplayNotifyPos = NONE;
 
     public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable,
             WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) {
@@ -127,6 +127,11 @@
     @Override
     public void changeCursor(Cursor c) {
         if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+        if (mCursor != null) {
+            callCursorPreClose(mCursor);
+        }
+
         super.changeCursor(c);
         if (c != null) {
             mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
@@ -135,39 +140,69 @@
             mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
             mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
         }
-        updateWorking();
     }
-        
+
+    /**
+     * Handle sending and receiving information associated with
+     * {@link DialogCursorProtocol#PRE_CLOSE}.
+     *
+     * @param cursor The cursor to call.
+     */
+    private void callCursorPreClose(Cursor cursor) {
+        final Bundle request = new Bundle();
+        request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.PRE_CLOSE);
+        request.putInt(DialogCursorProtocol.PRE_CLOSE_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
+        final Bundle response = cursor.respond(request);
+
+        mMaxDisplayed = -1;
+    }
+
     @Override
     public void notifyDataSetChanged() {
+        if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
         super.notifyDataSetChanged();
-        updateWorking();
-        if (mListItemToSelect != NO_ITEM_TO_SELECT) {
+
+        callCursorPostRefresh(mCursor);
+
+        // look out for the pending item we are supposed to scroll to
+        if (mListItemToSelect != NONE) {
             mSearchDialog.setListSelection(mListItemToSelect);
-            mListItemToSelect = NO_ITEM_TO_SELECT;
+            mListItemToSelect = NONE;
         }
     }
-    
-    /**
-     * Specifies the list item to select upon next call of {@link #notifyDataSetChanged()},
-     * in order to let us scroll the "More results..." list item to the top of the screen
-     * (or as close as it can get) when clicked.
-     */
-    public void setListItemToSelect(int index) {
-        mListItemToSelect = index;
-    }
-    
-    /**
-     * Updates the search dialog according to the current working status of the cursor.
-     */
-    private void updateWorking() {
-        if (!mGlobalSearchMode || mCursor == null) return;
-        
-        Bundle request = new Bundle();
-        request.putString(SearchManager.RESPOND_EXTRA_PENDING_SOURCES, "DUMMY");
-        Bundle response = mCursor.respond(request);
 
-        mSearchDialog.setWorking(response.getBoolean(SearchManager.RESPOND_EXTRA_PENDING_SOURCES));
+    /**
+     * Handle sending and receiving information associated with
+     * {@link DialogCursorProtocol#POST_REFRESH}.
+     * 
+     * @param cursor The cursor to call.
+     */
+    private void callCursorPostRefresh(Cursor cursor) {
+        final Bundle request = new Bundle();
+        request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.POST_REFRESH);
+        final Bundle response = cursor.respond(request);
+
+        mSearchDialog.setWorking(
+                response.getBoolean(DialogCursorProtocol.POST_REFRESH_RECEIVE_ISPENDING, false));
+
+        mDisplayNotifyPos =
+                response.getInt(DialogCursorProtocol.POST_REFRESH_RECEIVE_DISPLAY_NOTIFY, -1);
+    }
+
+    /**
+     * Tell the cursor which position was clicked, handling sending and receiving information
+     * associated with {@link DialogCursorProtocol#CLICK}.
+     *
+     * @param cursor The cursor
+     * @param position The position that was clicked.
+     */
+    void callCursorOnClick(Cursor cursor, int position) {
+        final Bundle request = new Bundle(1);
+        request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK);
+        request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position);
+        final Bundle response = cursor.respond(request);
+        mListItemToSelect = response.getInt(
+                DialogCursorProtocol.CLICK_RECEIVE_SELECTED_POS, SuggestionsAdapter.NONE);
     }
 
     /**
@@ -179,7 +214,7 @@
         v.setTag(new ChildViewCache(v));
         return v;
     }
-    
+
     /**
      * Cache of the child views of drop-drown list items, to avoid looking up the children
      * each time the contents of a list item are changed.
@@ -201,11 +236,22 @@
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
         ChildViewCache views = (ChildViewCache) view.getTag();
-        boolean isHtml = false;
-        if (mFormatCol >= 0) {
-            String format = cursor.getString(mFormatCol);
-            isHtml = "html".equals(format);    
+        final int pos = cursor.getPosition();
+
+        // update the maximum position displayed since last refresh
+        if (pos > mMaxDisplayed) {
+            mMaxDisplayed = pos;
         }
+
+        // if the cursor wishes to be notified about this position, send it
+        if (mDisplayNotifyPos != NONE && pos == mDisplayNotifyPos) {
+            final Bundle request = new Bundle();
+            request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.THRESH_HIT);
+            mCursor.respond(request);
+            mDisplayNotifyPos = NONE;  // only notify the first time
+        }
+
+        final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol));
         setViewText(cursor, views.mText1, mText1Col, isHtml);
         setViewText(cursor, views.mText2, mText2Col, isHtml);
         setViewIcon(cursor, views.mIcon1, mIconName1Col);
@@ -400,7 +446,7 @@
      */
     public static String getColumnString(Cursor cursor, String columnName) {
         int col = cursor.getColumnIndex(columnName);
-        if (col == NO_ITEM_TO_SELECT) {
+        if (col == NONE) {
             return null;
         }
         return cursor.getString(col);