Bug 1189719 - Recall and display search history within main browser UI.r=mcomella
authorAllison Naaktgeboren <ally@mozilla.com>
Tue, 01 Sep 2015 15:32:33 -0700
changeset 260404 5183c511ce583ee0ea00a6e057b2fa092a871290
parent 260396 d9753d7da8dfaf7583bf777ed6ee5535cb9b4393
child 260405 26b367ff0033d5730f4cd66930a3866eb3c89994
push id29310
push usercbook@mozilla.com
push dateWed, 02 Sep 2015 11:34:07 +0000
treeherdermozilla-central@fb805e859b4a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1189719
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1189719 - Recall and display search history within main browser UI.r=mcomella
mobile/android/base/db/BrowserContract.java
mobile/android/base/home/BrowserSearch.java
mobile/android/base/home/SearchEngineRow.java
mobile/android/base/resources/layout/suggestion_item.xml
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -470,16 +470,17 @@ public class BrowserContract {
     }
 
     @RobocopTarget
     public static final class SearchHistory implements CommonColumns, HistoryColumns {
         private SearchHistory() {}
 
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searchhistory";
         public static final String QUERY = "query";
+        public static final String DATE = "date";
         public static final String TABLE_NAME = "searchhistory";
 
         public static final Uri CONTENT_URI = Uri.withAppendedPath(SEARCH_HISTORY_AUTHORITY_URI, "searchhistory");
     }
 
     @RobocopTarget
     public static final class SuggestedSites implements CommonColumns, URLColumns {
         private SuggestedSites() {}
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -906,17 +906,17 @@ public class BrowserSearch extends HomeF
                 final SearchEngineRow row = (SearchEngineRow) view;
                 row.setOnUrlOpenListener(mUrlOpenListener);
                 row.setOnSearchListener(mSearchListener);
                 row.setOnEditSuggestionListener(mEditSuggestionListener);
                 row.setSearchTerm(mSearchTerm);
 
                 final SearchEngine engine = mSearchEngines.get(position);
                 final boolean animate = (mAnimateSuggestions && engine.hasSuggestions());
-                row.updateFromSearchEngine(engine, animate);
+                row.updateSuggestions(mSuggestionsEnabled, engine, mSearchTerm, animate);
                 if (animate) {
                     // Only animate suggestions the first time they are shown
                     mAnimateSuggestions = false;
                 }
             } else {
                 // Account for the search engines
                 position -= getPrimaryEngineCount();
 
--- a/mobile/android/base/home/SearchEngineRow.java
+++ b/mobile/android/base/home/SearchEngineRow.java
@@ -1,26 +1,29 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
+import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
 import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.widget.AnimatedHeightLayout;
 import org.mozilla.gecko.widget.FaviconView;
 import org.mozilla.gecko.widget.FlowLayout;
 
+import android.database.Cursor;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AlphaAnimation;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -128,17 +131,20 @@ class SearchEngineRow extends AnimatedHe
                                                          mSearchEngine.name, suggestion));
     }
 
     private String getSuggestionTextFromView(View v) {
         final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
         return suggestionText.getText().toString();
     }
 
-    private void setSuggestionOnView(View v, String suggestion) {
+    private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
+        final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
+        historyIcon.setVisibility(isUserSavedSearch ? View.VISIBLE: View.GONE);
+
         final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
         suggestionText.setText(suggestion);
         setDescriptionOnSuggestion(suggestionText, suggestion);
     }
 
     /**
      * Perform a search for the user-entered term.
      */
@@ -167,74 +173,119 @@ class SearchEngineRow extends AnimatedHe
     public void setOnSearchListener(OnSearchListener listener) {
         mSearchListener = listener;
     }
 
     public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
         mEditSuggestionListener = listener;
     }
 
-    public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
+    private void bindSuggestionView(String suggestion, boolean animate, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch){
+        final View suggestionItem;
+
+        // Reuse suggestion views from recycled view, if possible.
+        if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
+            suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
+            suggestionItem.setVisibility(View.VISIBLE);
+        } else {
+            suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
+
+            suggestionItem.setOnClickListener(mClickListener);
+            suggestionItem.setOnLongClickListener(mLongClickListener);
+
+            // Store the position of the suggestion for telemetry.
+            suggestionItem.setTag(String.valueOf(previousSuggestionChildIndex));
+
+            mSuggestionView.addView(suggestionItem);
+        }
+
+        setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
+
+        if (animate) {
+            AlphaAnimation anim = new AlphaAnimation(0, 1);
+            anim.setDuration(ANIMATION_DURATION);
+            anim.setStartOffset(previousSuggestionChildIndex * ANIMATION_DURATION);
+            suggestionItem.startAnimation(anim);
+        }
+    }
+
+    private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
+        // Hide extra suggestions that have been recycled.
+        for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
+            mSuggestionView.getChildAt(i).setVisibility(View.GONE);
+        }
+    }
+
+    private void updateFromSavedSearches(String searchTerm, boolean animate, int suggestionCounter, int recycledSuggestionCount) {
+        final ContentResolver cr = getContext().getContentResolver();
+
+        String[] columns = new String[] { SearchHistory.QUERY };
+        String sortOrderAndLimit = SearchHistory.DATE + " DESC";
+
+        final Cursor c = cr.query(SearchHistory.CONTENT_URI, columns, null, null, sortOrderAndLimit);
+        if (c == null) {
+            return;
+        }
+        try {
+            if (c.moveToFirst()) {
+                int counter = 0;
+                final int searchColumn = c.getColumnIndexOrThrow(SearchHistory.QUERY);
+                do {
+                    final String savedSearch = c.getString(searchColumn);
+                    if (counter == 4) {
+                        break;
+                    }
+                    // Bug 1200371 will move the filtering/matching and limit into the sql query
+                    if (savedSearch.startsWith(searchTerm)) {
+                        bindSuggestionView(savedSearch, animate, recycledSuggestionCount, suggestionCounter, true);
+                        ++suggestionCounter;
+                        ++counter;
+                    }
+                } while (c.moveToNext());
+            }
+        } finally {
+            c.close();
+        }
+        hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount);
+    }
+
+    private int updateFromSearchEngine(SearchEngine searchEngine, boolean animate, int recycledSuggestionCount) {
         // Update search engine reference.
         mSearchEngine = searchEngine;
 
         // Set the search engine icon (e.g., Google) for the row.
         mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier());
 
         // Set the initial content description.
         setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
 
-        // Add additional suggestions given by this engine.
-        final int recycledSuggestionCount = mSuggestionView.getChildCount();
-
         int suggestionCounter = 0;
+        // Apply Search Engine's suggestions
         for (String suggestion : mSearchEngine.getSuggestions()) {
-            final View suggestionItem;
-
-            // Reuse suggestion views from recycled view, if possible.
-            if (suggestionCounter + 1 < recycledSuggestionCount) {
-                suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1);
-                suggestionItem.setVisibility(View.VISIBLE);
-            } else {
-                suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
-
-                suggestionItem.setOnClickListener(mClickListener);
-                suggestionItem.setOnLongClickListener(mLongClickListener);
-
-                // Store the position of the suggestion for telemetry.
-                suggestionItem.setTag(String.valueOf(suggestionCounter));
-
-                final ImageView magnifier =
-                        (ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier);
-                magnifier.setVisibility(View.GONE);
-
-                mSuggestionView.addView(suggestionItem);
-            }
-
-            setSuggestionOnView(suggestionItem, suggestion);
-
-            if (animate) {
-                AlphaAnimation anim = new AlphaAnimation(0, 1);
-                anim.setDuration(ANIMATION_DURATION);
-                anim.setStartOffset(suggestionCounter * ANIMATION_DURATION);
-                suggestionItem.startAnimation(anim);
-            }
-
+            bindSuggestionView(suggestion, animate, recycledSuggestionCount, suggestionCounter, false);
             ++suggestionCounter;
         }
 
-        // Hide extra suggestions that have been recycled.
-        for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) {
-            mSuggestionView.getChildAt(i).setVisibility(View.GONE);
-        }
+        hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount);
 
         // Make sure mSelectedView is still valid.
         if (mSelectedView >= mSuggestionView.getChildCount()) {
             mSelectedView = mSuggestionView.getChildCount() - 1;
         }
+
+        return suggestionCounter;
+    }
+
+    public void updateSuggestions(boolean suggestionsEnabled, SearchEngine searchEngine, String searchTerm, boolean animate) {
+        // This can be called before the opt-in permission prompt is shown or set. Check first.
+        if (suggestionsEnabled) {
+            final int recycledSuggestionCount = mSuggestionView.getChildCount();
+            final int suggestionViewCount = updateFromSearchEngine(searchEngine, animate, recycledSuggestionCount);
+            updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
+        }
     }
 
     @Override
     public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
         final View suggestion = mSuggestionView.getChildAt(mSelectedView);
 
         if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
             return false;
--- a/mobile/android/base/resources/layout/suggestion_item.xml
+++ b/mobile/android/base/resources/layout/suggestion_item.xml
@@ -7,21 +7,22 @@
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:orientation="horizontal"
               android:background="@drawable/suggestion_selector"
               android:gravity="center_vertical"
               android:clickable="true"
               android:padding="7dp">
 
-    <ImageView android:id="@+id/suggestion_magnifier"
-               android:src="@drawable/search_icon_inactive"
+    <ImageView android:id="@+id/suggestion_item_icon"
+               android:src="@drawable/icon_most_recent_empty"
                android:layout_marginRight="3dip"
-               android:layout_width="16dip"
-               android:layout_height="16dip"/>
+               android:layout_width="18dip"
+               android:layout_height="18dip"
+               android:visibility="gone"/>
 
     <TextView android:id="@+id/suggestion_text"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:textColor="@color/placeholder_active_grey"
               android:textSize="14sp"
               android:gravity="center_vertical"
               android:layout_gravity="center_vertical"/>