Log when a search result is selected or search is abandoned (1/3)

* Populate a SearchState from the MultiSelectContactEntryListAdapter
  to with information about the number of results and partitions
  displayed to the user.  If a selection was made, record additional
  details.

Bug 26697731

Change-Id: I96de87ea1d297045421604ee0cd13c51c6c13dc4
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 9806c8c..f0ad19d 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -51,7 +51,6 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.Window;
 import android.widget.ImageButton;
 import android.widget.Toast;
 
@@ -87,6 +86,8 @@
 import com.android.contacts.list.ProviderStatusWatcher;
 import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
 import com.android.contacts.common.list.ViewPagerTabs;
+import com.android.contacts.common.logging.Logger;
+import com.android.contacts.common.logging.ScreenEvent;
 import com.android.contacts.preference.ContactsPreferenceActivity;
 import com.android.contacts.common.util.AccountFilterUtil;
 import com.android.contacts.common.util.ViewUtil;
@@ -564,13 +565,14 @@
         switch (action) {
             case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
                 mAllFragment.displayCheckBoxes(true);
-                // Fall through:
+                startSearchOrSelectionMode();
+                break;
             case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
-                // Tell the fragments that we're in the search mode or selection mode
-                configureFragments(false /* from request */);
-                updateFragmentsVisibility();
-                invalidateOptionsMenu();
-                showFabWithAnimation(/* showFabWithAnimation = */ false);
+                if (!mIsRecreatedInstance) {
+                    Logger.getInstance().logScreenView(
+                            ScreenEvent.SEARCH, this, ScreenEvent.TAG_SEARCH);
+                }
+                startSearchOrSelectionMode();
                 break;
             case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
                 showFabWithAnimation(/* showFabWithAnimation = */ true);
@@ -592,6 +594,13 @@
         }
     }
 
+    private void startSearchOrSelectionMode() {
+        configureFragments(false /* from request */);
+        updateFragmentsVisibility();
+        invalidateOptionsMenu();
+        showFabWithAnimation(/* showFabWithAnimation = */ false);
+    }
+
     @Override
     public void onSelectedTabChanged() {
         updateFragmentsVisibility();
@@ -1315,6 +1324,7 @@
         intent.putExtra(Intent.EXTRA_STREAM, uri);
         ImplicitIntentsUtil.startActivityOutsideApp(this, intent);
     }
+
     private void joinSelectedContacts() {
         JoinContactsDialogFragment.start(this, mAllFragment.getSelectedContactIds());
     }
@@ -1396,6 +1406,14 @@
             mAllFragment.displayCheckBoxes(false);
         } else if (mActionBarAdapter.isSearchMode()) {
             mActionBarAdapter.setSearchMode(false);
+
+            if (mAllFragment.wasSearchResultClicked()) {
+                mAllFragment.resetSearchResultClicked();
+            } else {
+                Logger.getInstance().logScreenView(
+                        ScreenEvent.SEARCH_EXIT, this, ScreenEvent.TAG_SEARCH_EXIT);
+                Logger.getInstance().logSearchEventImpl(mAllFragment.createSearchState());
+            }
         } else {
             super.onBackPressed();
         }
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 4d2eae8..1c5d7e7 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -19,14 +19,19 @@
 import com.android.contacts.common.list.ContactListAdapter;
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.logging.SearchState;
 import com.android.contacts.list.MultiSelectEntryContactListAdapter.SelectedContactsListener;
+import com.android.contacts.common.logging.Logger;
 
+import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.TreeSet;
 
 /**
@@ -44,12 +49,31 @@
 
     private static final String EXTRA_KEY_SELECTED_CONTACTS = "selected_contacts";
 
+    private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked";
+
     private OnCheckBoxListActionListener mCheckBoxListListener;
+    private boolean mSearchResultClicked;
 
     public void setCheckBoxListListener(OnCheckBoxListActionListener checkBoxListListener) {
         mCheckBoxListListener = checkBoxListListener;
     }
 
+    /**
+     * Whether a search result was clicked by the user. Tracked so that we can distinguish
+     * between exiting the search mode after a result was clicked from existing w/o clicking
+     * any search result.
+     */
+    public boolean wasSearchResultClicked() {
+        return mSearchResultClicked;
+    }
+
+    /**
+     * Resets whether a search result was clicked by the user to false.
+     */
+    public void resetSearchResultClicked() {
+        mSearchResultClicked = false;
+    }
+
     @Override
     public void onSelectedContactsChanged() {
         if (mCheckBoxListListener != null) {
@@ -77,6 +101,7 @@
             if (mCheckBoxListListener != null) {
                 mCheckBoxListListener.onSelectedContactIdsChanged();
             }
+            mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED);
         }
     }
 
@@ -100,6 +125,7 @@
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putSerializable(EXTRA_KEY_SELECTED_CONTACTS, getSelectedContactIds());
+        outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked);
     }
 
     public void displayCheckBoxes(boolean displayCheckBoxes) {
@@ -157,6 +183,10 @@
                 getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
             }
         } else {
+            mSearchResultClicked = true;
+            Logger.getInstance().logSearchEventImpl(
+                    createSearchStateForSearchResultClick(position));
+
             super.onItemClick(position, id);
         }
         if (mCheckBoxListListener != null && getAdapter().getSelectedContactIds().size() == 0) {
@@ -164,6 +194,73 @@
         }
     }
 
+    /**
+     * Returns the state of the search results currently presented to the user.
+     */
+    public SearchState createSearchState() {
+        return createSearchState(/* selectedPosition */ -1);
+    }
+
+    /**
+     * Returns the state of the search results presented to the user
+     * at the time the result in the given position was clicked.
+     */
+    public SearchState createSearchStateForSearchResultClick(int selectedPosition) {
+        return createSearchState(selectedPosition);
+    }
+
+    private SearchState createSearchState(int selectedPosition) {
+        final MultiSelectEntryContactListAdapter adapter = getAdapter();
+        if (adapter == null) {
+            return null;
+        }
+        final SearchState searchState = new SearchState();
+        searchState.queryLength = adapter.getQueryString() == null
+                ? 0 : adapter.getQueryString().length();
+        searchState.numPartitions = adapter.getPartitionCount();
+
+        // Set the number of results displayed to the user.  Note that the adapter.getCount(),
+        // value does not always match the number of results actually displayed to the user,
+        // which is why we calculate it manually.
+        final List<Integer> numResultsInEachPartition = new ArrayList<>();
+        for (int i = 0; i < adapter.getPartitionCount(); i++) {
+            final Cursor cursor = adapter.getCursor(i);
+            if (cursor == null || cursor.isClosed()) {
+                // Something went wrong, abort.
+                numResultsInEachPartition.clear();
+                break;
+            }
+            numResultsInEachPartition.add(cursor.getCount());
+        }
+        if (!numResultsInEachPartition.isEmpty()) {
+            int numResults = 0;
+            for (int i = 0; i < numResultsInEachPartition.size(); i++) {
+                numResults += numResultsInEachPartition.get(i);
+            }
+            searchState.numResults = numResults;
+        }
+
+        // If a selection was made, set additional search state
+        if (selectedPosition >= 0) {
+            searchState.selectedPartition = adapter.getPartitionForPosition(selectedPosition);
+            searchState.selectedIndexInPartition = adapter.getOffsetInPartition(selectedPosition);
+            final Cursor cursor = adapter.getCursor(searchState.selectedPartition);
+            searchState.numResultsInSelectedPartition =
+                    cursor == null || cursor.isClosed() ? -1 : cursor.getCount();
+
+            // Calculate the index across all partitions
+            if (!numResultsInEachPartition.isEmpty()) {
+                int selectedIndex = 0;
+                for (int i = 0; i < searchState.selectedPartition; i++) {
+                    selectedIndex += numResultsInEachPartition.get(i);
+                }
+                selectedIndex += searchState.selectedIndexInPartition;
+                searchState.selectedIndex = selectedIndex;
+            }
+        }
+        return searchState;
+    }
+
     @Override
     protected ContactListAdapter createListAdapter() {
         DefaultContactListAdapter adapter = new MultiSelectEntryContactListAdapter(getContext());