From c6801eb828627c37b8992584767c095dfe11df62 Mon Sep 17 00:00:00 2001 From: Jin Cao Date: Tue, 12 Aug 2014 18:16:57 -0700 Subject: Quantum search - version 1 I apologize in advance for this huge CL. - New icons (yay!) - Abandon framework search and handle in-app search functionality ourselves. This is made possible because we use Toolbar and can position any custom view on top. - Remove all previous search related attributes, including search providers, searchable, search authority, default search menu item layout, etc. - Cleaned up the recent history provider to contain only the functionalities we need - query, add, and delete. Instead of using the framework to add recent queries, we directly insert into our database. Since the provider no longer needs to extend ContentProvider, removed some unnecessary callbacks such as onCreate. - Custom quantum search views: - Top search bar, this is inserted in a FrameLayout on top of Toolbar. The search bar interacts with the suggestion list and AbstractActivityController via the QuantumSearchViewController interface. - Suggestions list, this is inserted in the FrameLayout that typically contains the main content pane for either one-pane or two-pane layouts. Again, this interacts with the action bar via the controller. - Voice search, this is simply an implicit intent that converts speech to text. b/16518233 Change-Id: I589c40e6c6e3d8c719856b735d0c53e8db986e65 --- AndroidManifest.xml | 11 +- res/drawable-hdpi/ic_arrow_back_24dp.png | Bin 0 -> 2977 bytes res/drawable-hdpi/ic_close_24dp.png | Bin 0 -> 3033 bytes res/drawable-hdpi/ic_history_24dp.png | Bin 0 -> 4068 bytes res/drawable-hdpi/ic_history_holo_light.png | Bin 749 -> 0 bytes res/drawable-hdpi/ic_mic_24dp.png | Bin 0 -> 3243 bytes res/drawable-mdpi/ic_arrow_back_24dp.png | Bin 0 -> 2884 bytes res/drawable-mdpi/ic_close_24dp.png | Bin 0 -> 2937 bytes res/drawable-mdpi/ic_history_24dp.png | Bin 0 -> 3480 bytes res/drawable-mdpi/ic_history_holo_light.png | Bin 507 -> 0 bytes res/drawable-mdpi/ic_mic_24dp.png | Bin 0 -> 3029 bytes res/drawable-xhdpi/ic_arrow_back_24dp.png | Bin 0 -> 3017 bytes res/drawable-xhdpi/ic_close_24dp.png | Bin 0 -> 3141 bytes res/drawable-xhdpi/ic_history_24dp.png | Bin 0 -> 4360 bytes res/drawable-xhdpi/ic_history_holo_light.png | Bin 916 -> 0 bytes res/drawable-xhdpi/ic_mic_24dp.png | Bin 0 -> 3416 bytes res/drawable-xxhdpi/ic_arrow_back_24dp.png | Bin 0 -> 3152 bytes res/drawable-xxhdpi/ic_close_24dp.png | Bin 0 -> 3316 bytes res/drawable-xxhdpi/ic_history_24dp.png | Bin 0 -> 5270 bytes res/drawable-xxhdpi/ic_history_holo_light.png | Bin 2915 -> 0 bytes res/drawable-xxhdpi/ic_mic_24dp.png | Bin 0 -> 3804 bytes res/drawable-xxxhdpi/ic_arrow_back_24dp.png | Bin 0 -> 3355 bytes res/drawable-xxxhdpi/ic_close_24dp.png | Bin 0 -> 3504 bytes res/drawable-xxxhdpi/ic_history_24dp.png | Bin 0 -> 6501 bytes res/drawable-xxxhdpi/ic_mic_24dp.png | Bin 0 -> 4215 bytes res/drawable/action_bar_shadow.xml | 24 +++ res/layout/mail_actionbar_searchview.xml | 48 ++++- res/layout/one_pane_activity.xml | 13 +- res/layout/search_suggestion_item.xml | 47 +++++ res/layout/search_suggestion_list.xml | 54 ++++++ res/layout/toolbar_with_search.xml | 33 ++++ res/layout/two_pane_activity.xml | 87 +++++---- res/menu-sw600dp-land/conversation_actions.xml | 3 +- .../conversation_list_search_results_actions.xml | 3 +- res/menu/conversation_list_menu.xml | 3 +- .../conversation_list_search_results_actions.xml | 3 +- res/values-ldrtl/styles-ldrtl.xml | 7 + res/values-sw600dp-land/dimen.xml | 20 -- res/values-sw600dp/themes.xml | 2 - res/values/colors.xml | 3 + res/values/dimen.xml | 4 +- res/values/strings.xml | 8 +- res/values/styles.xml | 10 +- res/values/themes.xml | 7 + res/xml/searchable.xml | 28 --- .../providers/SearchRecentSuggestionsProvider.java | 175 ++++------------- .../mail/providers/SuggestionsProvider.java | 19 +- .../mail/ui/AbstractActivityController.java | 54 ++++-- src/com/android/mail/ui/ActionBarController.java | 152 +-------------- src/com/android/mail/ui/ActivityController.java | 2 + src/com/android/mail/ui/ControllableActivity.java | 3 + .../android/mail/ui/ConversationListFragment.java | 1 - .../android/mail/ui/FolderSelectionActivity.java | 9 +- src/com/android/mail/ui/MailActivity.java | 7 + .../android/mail/ui/MaterialSearchActionView.java | 148 ++++++++++++++ .../mail/ui/MaterialSearchSuggestionsList.java | 214 +++++++++++++++++++++ .../mail/ui/MaterialSearchViewController.java | 197 +++++++++++++++++++ src/com/android/mail/ui/OnePaneController.java | 6 + .../android/mail/ui/SearchActionBarController.java | 120 ------------ src/com/android/mail/ui/TwoPaneController.java | 9 +- .../mail/ui/settings/GeneralPrefsFragment.java | 9 +- .../providers/UnifiedAccountCacheProvider.java | 12 +- 62 files changed, 967 insertions(+), 588 deletions(-) create mode 100644 res/drawable-hdpi/ic_arrow_back_24dp.png create mode 100644 res/drawable-hdpi/ic_close_24dp.png create mode 100644 res/drawable-hdpi/ic_history_24dp.png delete mode 100644 res/drawable-hdpi/ic_history_holo_light.png create mode 100644 res/drawable-hdpi/ic_mic_24dp.png create mode 100644 res/drawable-mdpi/ic_arrow_back_24dp.png create mode 100644 res/drawable-mdpi/ic_close_24dp.png create mode 100644 res/drawable-mdpi/ic_history_24dp.png delete mode 100644 res/drawable-mdpi/ic_history_holo_light.png create mode 100644 res/drawable-mdpi/ic_mic_24dp.png create mode 100644 res/drawable-xhdpi/ic_arrow_back_24dp.png create mode 100644 res/drawable-xhdpi/ic_close_24dp.png create mode 100644 res/drawable-xhdpi/ic_history_24dp.png delete mode 100644 res/drawable-xhdpi/ic_history_holo_light.png create mode 100644 res/drawable-xhdpi/ic_mic_24dp.png create mode 100644 res/drawable-xxhdpi/ic_arrow_back_24dp.png create mode 100644 res/drawable-xxhdpi/ic_close_24dp.png create mode 100644 res/drawable-xxhdpi/ic_history_24dp.png delete mode 100644 res/drawable-xxhdpi/ic_history_holo_light.png create mode 100644 res/drawable-xxhdpi/ic_mic_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_arrow_back_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_close_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_history_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_mic_24dp.png create mode 100644 res/drawable/action_bar_shadow.xml create mode 100644 res/layout/search_suggestion_item.xml create mode 100644 res/layout/search_suggestion_list.xml create mode 100644 res/layout/toolbar_with_search.xml delete mode 100644 res/values-sw600dp-land/dimen.xml delete mode 100644 res/xml/searchable.xml create mode 100644 src/com/android/mail/ui/MaterialSearchActionView.java create mode 100644 src/com/android/mail/ui/MaterialSearchSuggestionsList.java create mode 100644 src/com/android/mail/ui/MaterialSearchViewController.java delete mode 100644 src/com/android/mail/ui/SearchActionBarController.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a8dd8d764..3c5b30e2d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -43,7 +43,8 @@ android:label="@string/app_name" android:name=".ui.MailActivity" android:launchMode="singleTop" - android:theme="@style/MailActivityTheme" > + android:theme="@style/MailActivityTheme" + android:windowSoftInputMode="stateHidden"> @@ -58,7 +59,6 @@ - @@ -159,13 +159,6 @@ - - - diff --git a/res/drawable-hdpi/ic_arrow_back_24dp.png b/res/drawable-hdpi/ic_arrow_back_24dp.png new file mode 100644 index 000000000..9b5f436f6 Binary files /dev/null and b/res/drawable-hdpi/ic_arrow_back_24dp.png differ diff --git a/res/drawable-hdpi/ic_close_24dp.png b/res/drawable-hdpi/ic_close_24dp.png new file mode 100644 index 000000000..33db9eef6 Binary files /dev/null and b/res/drawable-hdpi/ic_close_24dp.png differ diff --git a/res/drawable-hdpi/ic_history_24dp.png b/res/drawable-hdpi/ic_history_24dp.png new file mode 100644 index 000000000..b93e92940 Binary files /dev/null and b/res/drawable-hdpi/ic_history_24dp.png differ diff --git a/res/drawable-hdpi/ic_history_holo_light.png b/res/drawable-hdpi/ic_history_holo_light.png deleted file mode 100644 index d3feeac20..000000000 Binary files a/res/drawable-hdpi/ic_history_holo_light.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_mic_24dp.png b/res/drawable-hdpi/ic_mic_24dp.png new file mode 100644 index 000000000..143ccc43a Binary files /dev/null and b/res/drawable-hdpi/ic_mic_24dp.png differ diff --git a/res/drawable-mdpi/ic_arrow_back_24dp.png b/res/drawable-mdpi/ic_arrow_back_24dp.png new file mode 100644 index 000000000..3a5800d1e Binary files /dev/null and b/res/drawable-mdpi/ic_arrow_back_24dp.png differ diff --git a/res/drawable-mdpi/ic_close_24dp.png b/res/drawable-mdpi/ic_close_24dp.png new file mode 100644 index 000000000..bd39f9332 Binary files /dev/null and b/res/drawable-mdpi/ic_close_24dp.png differ diff --git a/res/drawable-mdpi/ic_history_24dp.png b/res/drawable-mdpi/ic_history_24dp.png new file mode 100644 index 000000000..76b0a9178 Binary files /dev/null and b/res/drawable-mdpi/ic_history_24dp.png differ diff --git a/res/drawable-mdpi/ic_history_holo_light.png b/res/drawable-mdpi/ic_history_holo_light.png deleted file mode 100644 index 2b6eec4f5..000000000 Binary files a/res/drawable-mdpi/ic_history_holo_light.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_mic_24dp.png b/res/drawable-mdpi/ic_mic_24dp.png new file mode 100644 index 000000000..81d0c21ad Binary files /dev/null and b/res/drawable-mdpi/ic_mic_24dp.png differ diff --git a/res/drawable-xhdpi/ic_arrow_back_24dp.png b/res/drawable-xhdpi/ic_arrow_back_24dp.png new file mode 100644 index 000000000..73395b0ac Binary files /dev/null and b/res/drawable-xhdpi/ic_arrow_back_24dp.png differ diff --git a/res/drawable-xhdpi/ic_close_24dp.png b/res/drawable-xhdpi/ic_close_24dp.png new file mode 100644 index 000000000..81efb7040 Binary files /dev/null and b/res/drawable-xhdpi/ic_close_24dp.png differ diff --git a/res/drawable-xhdpi/ic_history_24dp.png b/res/drawable-xhdpi/ic_history_24dp.png new file mode 100644 index 000000000..61c0bfe34 Binary files /dev/null and b/res/drawable-xhdpi/ic_history_24dp.png differ diff --git a/res/drawable-xhdpi/ic_history_holo_light.png b/res/drawable-xhdpi/ic_history_holo_light.png deleted file mode 100644 index 83d61fa68..000000000 Binary files a/res/drawable-xhdpi/ic_history_holo_light.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_mic_24dp.png b/res/drawable-xhdpi/ic_mic_24dp.png new file mode 100644 index 000000000..1dcf56c4e Binary files /dev/null and b/res/drawable-xhdpi/ic_mic_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_arrow_back_24dp.png b/res/drawable-xxhdpi/ic_arrow_back_24dp.png new file mode 100644 index 000000000..650351d06 Binary files /dev/null and b/res/drawable-xxhdpi/ic_arrow_back_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_close_24dp.png b/res/drawable-xxhdpi/ic_close_24dp.png new file mode 100644 index 000000000..0924e29a2 Binary files /dev/null and b/res/drawable-xxhdpi/ic_close_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_history_24dp.png b/res/drawable-xxhdpi/ic_history_24dp.png new file mode 100644 index 000000000..4fe0a7efe Binary files /dev/null and b/res/drawable-xxhdpi/ic_history_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_history_holo_light.png b/res/drawable-xxhdpi/ic_history_holo_light.png deleted file mode 100644 index b5e82bc69..000000000 Binary files a/res/drawable-xxhdpi/ic_history_holo_light.png and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_mic_24dp.png b/res/drawable-xxhdpi/ic_mic_24dp.png new file mode 100644 index 000000000..ce9890478 Binary files /dev/null and b/res/drawable-xxhdpi/ic_mic_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_arrow_back_24dp.png b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png new file mode 100644 index 000000000..8068c1b57 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_close_24dp.png b/res/drawable-xxxhdpi/ic_close_24dp.png new file mode 100644 index 000000000..cb7a27ca8 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_close_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_history_24dp.png b/res/drawable-xxxhdpi/ic_history_24dp.png new file mode 100644 index 000000000..420576b08 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_history_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_mic_24dp.png b/res/drawable-xxxhdpi/ic_mic_24dp.png new file mode 100644 index 000000000..c586bb535 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_mic_24dp.png differ diff --git a/res/drawable/action_bar_shadow.xml b/res/drawable/action_bar_shadow.xml new file mode 100644 index 000000000..32501171d --- /dev/null +++ b/res/drawable/action_bar_shadow.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/res/layout/mail_actionbar_searchview.xml b/res/layout/mail_actionbar_searchview.xml index 730f1708a..b7bc1e662 100644 --- a/res/layout/mail_actionbar_searchview.xml +++ b/res/layout/mail_actionbar_searchview.xml @@ -1,6 +1,6 @@ - + android:layout_height="match_parent" + android:background="@android:color/white"> + + + + + + + + \ No newline at end of file diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml index 478edb9a9..40072a2a8 100644 --- a/res/layout/one_pane_activity.xml +++ b/res/layout/one_pane_activity.xml @@ -16,7 +16,6 @@ --> @@ -27,12 +26,8 @@ android:layout_height="match_parent" android:orientation="vertical"> - + + + + @@ -57,4 +54,4 @@ android:layout_height="match_parent" android:layout_gravity="start" /> - + \ No newline at end of file diff --git a/res/layout/search_suggestion_item.xml b/res/layout/search_suggestion_item.xml new file mode 100644 index 000000000..49b0256c9 --- /dev/null +++ b/res/layout/search_suggestion_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/search_suggestion_list.xml b/res/layout/search_suggestion_list.xml new file mode 100644 index 000000000..d024ccfe5 --- /dev/null +++ b/res/layout/search_suggestion_list.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/toolbar_with_search.xml b/res/layout/toolbar_with_search.xml new file mode 100644 index 000000000..4e5abda14 --- /dev/null +++ b/res/layout/toolbar_with_search.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/two_pane_activity.xml b/res/layout/two_pane_activity.xml index 2f8f6db6e..eb194c96c 100644 --- a/res/layout/two_pane_activity.xml +++ b/res/layout/two_pane_activity.xml @@ -15,49 +15,68 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:orientation="vertical"> + + + + + android:layout_width="match_parent" + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:background="@color/tablet_background_gray"> - + - - - - - + + + + + - + - + - + + + + + + + + + + + - + \ No newline at end of file diff --git a/res/menu-sw600dp-land/conversation_actions.xml b/res/menu-sw600dp-land/conversation_actions.xml index 78325b8ce..af888ddde 100644 --- a/res/menu-sw600dp-land/conversation_actions.xml +++ b/res/menu-sw600dp-land/conversation_actions.xml @@ -22,8 +22,7 @@ + android:icon="@drawable/ic_menu_search" /> + android:icon="@drawable/ic_menu_search" /> + android:icon="@drawable/ic_menu_search" /> + android:icon="@drawable/ic_menu_search" /> @dimen/undo_icon_padding_end + + - + + \ No newline at end of file diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml deleted file mode 100644 index 5607f16cb..000000000 --- a/res/xml/searchable.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java b/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java index 99ac4dfa4..0581869ba 100644 --- a/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java +++ b/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java @@ -18,37 +18,30 @@ package com.android.mail.providers; import android.app.SearchManager; -import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; +import android.os.SystemClock; import android.text.TextUtils; import com.android.mail.R; import java.util.ArrayList; -public class SearchRecentSuggestionsProvider extends ContentProvider { +public class SearchRecentSuggestionsProvider { /* * String used to delimit different parts of a query. */ public static final String QUERY_TOKEN_SEPARATOR = " "; - // client-provided configuration values - private String mAuthority; - private int mMode; - // general database configuration and tables private SQLiteOpenHelper mOpenHelper; private static final String sDatabaseName = "suggestions.db"; private static final String sSuggestions = "suggestions"; private static final String ORDER_BY = "date DESC"; - private static final String NULL_COLUMN = "query"; // Table of database versions. Don't forget to update! // NOTE: These version values are shifted left 8 bits (x 256) in order to create space for @@ -56,24 +49,28 @@ public class SearchRecentSuggestionsProvider extends ContentProvider { // // 1 original implementation with queries, and 1 or 2 display columns // 1->2 added UNIQUE constraint to display1 column - private static final int DATABASE_VERSION = 2 * 256; + private static final int DATABASE_VERSION = 3 * 256; /** * This mode bit configures the database to record recent queries. required - * - * @see #setupSuggestions(String, int) + * @see #setupSuggestions(int) */ public static final int DATABASE_MODE_QUERIES = 1; - // Uri and query support - private static final int URI_MATCH_SUGGEST = 1; - - private Uri mSuggestionsUri; - private UriMatcher mUriMatcher; - private String mSuggestSuggestionClause; private String[] mSuggestionProjection; + protected final Context mContext; + + public SearchRecentSuggestionsProvider(Context context) { + mContext = context; + mOpenHelper = new DatabaseHelper(mContext, DATABASE_VERSION); + } + + public void cleanup() { + mOpenHelper.close(); + } + /** * Builds the database. This version has extra support for using the version field * as a mode flags field, and configures the database columns depending on the mode bits @@ -110,31 +107,20 @@ public class SearchRecentSuggestionsProvider extends ContentProvider { * constructor. In your application or activities, you must provide the same values when * you create the {@link android.provider.SearchRecentSuggestions} helper. * - * @param authority This must match the authority that you've declared in your manifest. * @param mode You can use mode flags here to determine certain functional aspects of your * database. Note, this value should not change from run to run, because when it does change, * your suggestions database may be wiped. * * @see #DATABASE_MODE_QUERIES */ - protected void setupSuggestions(String authority, int mode) { - if (TextUtils.isEmpty(authority) || - ((mode & DATABASE_MODE_QUERIES) == 0)) { + protected void setupSuggestions(int mode) { + if ((mode & DATABASE_MODE_QUERIES) == 0) { throw new IllegalArgumentException(); } - // saved values - mAuthority = new String(authority); - mMode = mode; - - // derived values - mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions"); - mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mUriMatcher.addURI(mAuthority, SearchManager.SUGGEST_URI_PATH_QUERY, URI_MATCH_SUGGEST); - // The URI of the icon that we will include on every suggestion here. final String historicalIcon = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" - + getContext().getPackageName() + "/" + R.drawable.ic_history_holo_light; + + mContext.getPackageName() + "/" + R.drawable.ic_history_24dp; mSuggestSuggestionClause = "display1 LIKE ?"; mSuggestionProjection = new String [] { @@ -145,99 +131,6 @@ public class SearchRecentSuggestionsProvider extends ContentProvider { }; } - /** - * This method is provided for use by the ContentResolver. Do not override, or directly - * call from your own code. - */ - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - - final int length = uri.getPathSegments().size(); - if (length != 1) { - throw new IllegalArgumentException("Unknown Uri"); - } - - final String base = uri.getPathSegments().get(0); - int count = 0; - if (base.equals(sSuggestions)) { - count = db.delete(sSuggestions, selection, selectionArgs); - } else { - throw new IllegalArgumentException("Unknown Uri"); - } - getContext().getContentResolver().notifyChange(uri, null); - return count; - } - - /** - * This method is provided for use by the ContentResolver. Do not override, or directly - * call from your own code. - */ - @Override - public String getType(Uri uri) { - if (mUriMatcher.match(uri) == URI_MATCH_SUGGEST) { - return SearchManager.SUGGEST_MIME_TYPE; - } - int length = uri.getPathSegments().size(); - if (length >= 1) { - String base = uri.getPathSegments().get(0); - if (base.equals(sSuggestions)) { - if (length == 1) { - return "vnd.android.cursor.dir/suggestion"; - } else if (length == 2) { - return "vnd.android.cursor.item/suggestion"; - } - } - } - throw new IllegalArgumentException("Unknown Uri"); - } - - /** - * This method is provided for use by the ContentResolver. Do not override, or directly - * call from your own code. - */ - @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - - int length = uri.getPathSegments().size(); - if (length < 1) { - throw new IllegalArgumentException("Unknown Uri"); - } - // Note: This table has on-conflict-replace semantics, so insert() may actually replace() - long rowID = -1; - String base = uri.getPathSegments().get(0); - Uri newUri = null; - if (base.equals(sSuggestions)) { - if (length == 1) { - rowID = db.insert(sSuggestions, NULL_COLUMN, values); - if (rowID > 0) { - newUri = Uri.withAppendedPath(mSuggestionsUri, String.valueOf(rowID)); - } - } - } - if (rowID < 0) { - throw new IllegalArgumentException("Unknown Uri"); - } - getContext().getContentResolver().notifyChange(newUri, null); - return newUri; - } - - /** - * This method is provided for use by the ContentResolver. Do not override, or directly - * call from your own code. - */ - @Override - public boolean onCreate() { - if (mAuthority == null || mMode == 0) { - throw new IllegalArgumentException("Provider not configured"); - } - int mWorkingDbVersion = DATABASE_VERSION + mMode; - mOpenHelper = new DatabaseHelper(getContext(), mWorkingDbVersion); - - return true; - } - private ArrayList mFullQueryTerms; /** @@ -278,13 +171,8 @@ public class SearchRecentSuggestionsProvider extends ContentProvider { mFullQueryTerms = terms; } - /** - * This method is provided for use by the ContentResolver. Do not override, - * or directly call from your own code. - */ // TODO: Confirm no injection attacks here, or rewrite. - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); @@ -300,22 +188,27 @@ public class SearchRecentSuggestionsProvider extends ContentProvider { suggestSelection = mSuggestSuggestionClause; } // Suggestions are always performed with the default sort order - // Add this to the query: - // "select 'real_query' as SearchManager.SUGGEST_COLUMN_QUERY. - // rest of query - // real query will then show up in the suggestion Cursor c = db.query(sSuggestions, createProjection(selectionArgs), suggestSelection, myArgs, null, null, ORDER_BY, null); - c.setNotificationUri(getContext().getContentResolver(), uri); return c; } /** - * This method is provided for use by the ContentResolver. Do not override, or directly - * call from your own code. + * We are going to keep track of recent suggestions ourselves and not depend on the framework. + * Note that this writes to disk. DO NOT CALL FROM MAIN THREAD. */ - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("Not implemented"); + public void saveRecentQuery(String query) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + ContentValues values = new ContentValues(3); + values.put("display1", query); + values.put("query", query); + values.put("date", SystemClock.elapsedRealtime()); + // Note: This table has on-conflict-replace semantics, so insert() may actually replace() + db.insert(sSuggestions, null, values); + } + + public void clearHistory() { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + db.delete(sSuggestions, null, null); } } \ No newline at end of file diff --git a/src/com/android/mail/providers/SuggestionsProvider.java b/src/com/android/mail/providers/SuggestionsProvider.java index 92c7bccf9..7efaed3f6 100644 --- a/src/com/android/mail/providers/SuggestionsProvider.java +++ b/src/com/android/mail/providers/SuggestionsProvider.java @@ -65,20 +65,13 @@ public class SuggestionsProvider extends SearchRecentSuggestionsProvider { */ static private final int MIN_QUERY_LENGTH_FOR_CONTACTS = 2; - public SuggestionsProvider() { - super(); + public SuggestionsProvider(Context context) { + super(context); + setupSuggestions(MODE); } @Override - public boolean onCreate() { - final String authority = getContext().getString(R.string.suggestions_authority); - setupSuggestions(authority, MODE); - super.onCreate(); - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { String query = selectionArgs[0]; MergeCursor mergeCursor = null; @@ -110,7 +103,7 @@ public class SuggestionsProvider extends SearchRecentSuggestionsProvider { ArrayList cursors = new ArrayList(); // Pass query; at this point it is either the last term OR the // only term. - cursors.add(super.query(uri, projection, selection, new String[] { query }, sortOrder)); + cursors.add(super.query(projection, selection, new String[] { query }, sortOrder)); if (query.length() >= MIN_QUERY_LENGTH_FOR_CONTACTS) { cursors.add(new ContactsCursor().query(query)); @@ -124,10 +117,8 @@ public class SuggestionsProvider extends SearchRecentSuggestionsProvider { * Utility class to return a cursor over the contacts database */ private final class ContactsCursor extends MatrixCursorWithCachedColumns { - private final Context mContext; public ContactsCursor() { super(CONTACTS_COLUMNS); - mContext = getContext(); } /** diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java index d619474c8..ee2e3b104 100644 --- a/src/com/android/mail/ui/AbstractActivityController.java +++ b/src/com/android/mail/ui/AbstractActivityController.java @@ -46,7 +46,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.os.SystemClock; -import android.provider.SearchRecentSuggestions; +import android.speech.RecognizerIntent; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; @@ -87,7 +87,6 @@ import com.android.mail.providers.Folder; import com.android.mail.providers.FolderWatcher; import com.android.mail.providers.MailAppProvider; import com.android.mail.providers.Settings; -import com.android.mail.providers.SuggestionsProvider; import com.android.mail.providers.UIProvider; import com.android.mail.providers.UIProvider.AccountCapabilities; import com.android.mail.providers.UIProvider.AccountCursorExtraKeys; @@ -196,6 +195,7 @@ public abstract class AbstractActivityController implements ActivityController, protected final RecentFolderList mRecentFolderList; protected ConversationListContext mConvListContext; protected Conversation mCurrentConversation; + protected MaterialSearchViewController mSearchViewController; /** * The hash of {@link #mCurrentConversation} in detached mode. 0 if we are not in detached mode. */ @@ -610,12 +610,9 @@ public abstract class AbstractActivityController implements ActivityController, return; } - final boolean isSearch = mActivity.getIntent() != null - && Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()); - mActionBarController = isSearch ? - new SearchActionBarController(mContext) : - new ActionBarController(mContext); + mActionBarController = new ActionBarController(mContext); mActionBarController.initialize(mActivity, this, actionBar); + actionBar.setShowHideAnimationEnabled(false); // init the action bar to allow the 'up' affordance. // any configurations that disallow 'up' should do that later. @@ -1117,7 +1114,10 @@ public abstract class AbstractActivityController implements ActivityController, } break; case CHANGE_NAVIGATION_REQUEST_CODE: - if (resultCode == Activity.RESULT_OK && data != null) { + if (ViewMode.isSearchMode(mViewMode.getMode())) { + mActivity.setResult(resultCode, data); + mActivity.finish(); + } else if (resultCode == Activity.RESULT_OK && data != null) { // We have have received a result that indicates we need to navigate to a // different folder or account. This happens if someone navigates using the // drawer on the search results activity. @@ -1132,6 +1132,17 @@ public abstract class AbstractActivityController implements ActivityController, } } break; + case MaterialSearchViewController.VOICE_SEARCH_REQUEST_CODE: + if (resultCode == Activity.RESULT_OK) { + final ArrayList matches = data.getStringArrayListExtra( + RecognizerIntent.EXTRA_RESULTS); + if (!matches.isEmpty()) { + // not sure how dependable the API is, but it's all we have. + // take the top choice. + mSearchViewController.onSearchPerformed(matches.get(0)); + } + } + break; } } @@ -1306,6 +1317,9 @@ public abstract class AbstractActivityController implements ActivityController, final Intent intent = mActivity.getIntent(); + mSearchViewController = new MaterialSearchViewController(mActivity, this, intent, + savedState); + // Immediately handle a clean launch with intent, and any state restoration // that does not rely on restored fragments or loader data // any state restoration that relies on those can be done later in @@ -1596,6 +1610,9 @@ public abstract class AbstractActivityController implements ActivityController, showEmptyDialog(); } else if (id == R.id.empty_spam) { showEmptyDialog(); + } else if (id == R.id.search) { + mSearchViewController.showSearchActionBar( + MaterialSearchViewController.SEARCH_VIEW_STATE_VISIBLE); } else { handled = false; } @@ -1665,6 +1682,8 @@ public abstract class AbstractActivityController implements ActivityController, if (isDrawerEnabled() && mDrawerContainer.isDrawerVisible(mDrawerPullout)) { mDrawerContainer.closeDrawers(); return true; + } else if (mSearchViewController.handleBackPress()) { + return true; } return handleBackPress(); @@ -2124,6 +2143,8 @@ public abstract class AbstractActivityController implements ActivityController, outState.putBundle(SAVED_CONVERSATION_LIST_SCROLL_POSITIONS, mConversationListScrollPositions); + + mSearchViewController.saveState(outState); } /** @@ -2141,7 +2162,8 @@ public abstract class AbstractActivityController implements ActivityController, intent.putExtra(ConversationListContext.EXTRA_SEARCH_QUERY, query); intent.putExtra(Utils.EXTRA_ACCOUNT, mAccount); intent.setComponent(mActivity.getComponentName()); - mActionBarController.collapseSearch(); + mSearchViewController.showSearchActionBar( + MaterialSearchViewController.SEARCH_VIEW_STATE_GONE); // Call startActivityForResult here so we can tell if we have navigated to a different folder // or account from search results. mActivity.startActivityForResult(intent, CHANGE_NAVIGATION_REQUEST_CODE); @@ -2167,6 +2189,7 @@ public abstract class AbstractActivityController implements ActivityController, mDestroyed = true; mHandler.removeCallbacks(mLogServiceChecker); mLogServiceChecker = null; + mSearchViewController.onDestroy(); } /** @@ -2403,12 +2426,9 @@ public abstract class AbstractActivityController implements ActivityController, } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) { if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) { mHaveSearchResults = false; - // Save this search query for future suggestions. + // Save this search query for future suggestions final String query = intent.getStringExtra(SearchManager.QUERY); - final String authority = mContext.getString(R.string.suggestions_authority); - final SearchRecentSuggestions suggestions = new SearchRecentSuggestions( - mContext, authority, SuggestionsProvider.MODE); - suggestions.saveRecentQuery(query, null); + mSearchViewController.saveRecentQuery(query); setAccount((Account) intent.getParcelableExtra(Utils.EXTRA_ACCOUNT)); fetchSearchFolder(intent); if (shouldEnterSearchConvMode()) { @@ -3239,7 +3259,8 @@ public abstract class AbstractActivityController implements ActivityController, return; } if (mAccount.supportsSearch()) { - mActionBarController.expandSearch(); + mSearchViewController.showSearchActionBar( + MaterialSearchViewController.SEARCH_VIEW_STATE_VISIBLE); } else { Toast.makeText(mActivity.getActivityContext(), mActivity.getActivityContext() .getString(R.string.search_unsupported), Toast.LENGTH_SHORT).show(); @@ -4559,10 +4580,9 @@ public abstract class AbstractActivityController implements ActivityController, // TODO: Fold this into the outer class when b/16627877 is fixed private class HomeButtonListener implements View.OnClickListener { - @Override public void onClick(View v) { onUpPressed(); } } -} +} \ No newline at end of file diff --git a/src/com/android/mail/ui/ActionBarController.java b/src/com/android/mail/ui/ActionBarController.java index c73ebd0f5..f0b51afbe 100644 --- a/src/com/android/mail/ui/ActionBarController.java +++ b/src/com/android/mail/ui/ActionBarController.java @@ -17,31 +17,22 @@ package com.android.mail.ui; -import android.app.SearchManager; -import android.app.SearchableInfo; import android.content.ContentResolver; import android.content.Context; -import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBar; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.SearchView.OnQueryTextListener; -import android.support.v7.widget.SearchView.OnSuggestionListener; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; -import com.android.mail.ConversationListContext; import com.android.mail.R; import com.android.mail.providers.Account; import com.android.mail.providers.AccountObserver; import com.android.mail.providers.Conversation; import com.android.mail.providers.Folder; import com.android.mail.providers.FolderObserver; -import com.android.mail.providers.SearchRecentSuggestionsProvider; import com.android.mail.providers.UIProvider; import com.android.mail.providers.UIProvider.AccountCapabilities; import com.android.mail.providers.UIProvider.FolderCapabilities; @@ -53,8 +44,7 @@ import com.android.mail.utils.Utils; /** * Controller to manage the various states of the {@link android.app.ActionBar}. */ -public class ActionBarController implements ViewMode.ModeChangeListener, - OnQueryTextListener, OnSuggestionListener, MenuItemCompat.OnActionExpandListener { +public class ActionBarController implements ViewMode.ModeChangeListener { private final Context mContext; @@ -75,8 +65,6 @@ public class ActionBarController implements ViewMode.ModeChangeListener, */ private Folder mFolder; - private SearchView mSearchWidget; - private MenuItem mSearch; private MenuItem mEmptyTrashItem; private MenuItem mEmptySpamItem; @@ -117,46 +105,9 @@ public class ActionBarController implements ViewMode.ModeChangeListener, mIsOnTablet = Utils.useTabletUI(context.getResources()); } - public void expandSearch() { - if (mSearch != null) { - MenuItemCompat.expandActionView(mSearch); - } - } - - /** - * Close the search view if it is expanded. - */ - public void collapseSearch() { - if (mSearch != null) { - MenuItemCompat.collapseActionView(mSearch); - } - } - - /** - * Get the search menu item. - */ - protected MenuItem getSearch() { - return mSearch; - } - public boolean onCreateOptionsMenu(Menu menu) { mEmptyTrashItem = menu.findItem(R.id.empty_trash); mEmptySpamItem = menu.findItem(R.id.empty_spam); - mSearch = menu.findItem(R.id.search); - - if (mSearch != null) { - mSearchWidget = (SearchView) MenuItemCompat.getActionView(mSearch); - MenuItemCompat.setOnActionExpandListener(mSearch, this); - SearchManager searchManager = (SearchManager) mActivity.getActivityContext() - .getSystemService(Context.SEARCH_SERVICE); - if (searchManager != null && mSearchWidget != null) { - SearchableInfo info = searchManager.getSearchableInfo(mActivity.getComponentName()); - mSearchWidget.setSearchableInfo(info); - mSearchWidget.setOnQueryTextListener(this); - mSearchWidget.setOnSuggestionListener(this); - mSearchWidget.setIconifiedByDefault(true); - } - } // the menu should be displayed if the mode is known return getMode() != ViewMode.UNKNOWN; @@ -245,7 +196,6 @@ public class ActionBarController implements ViewMode.ModeChangeListener, break; case ViewMode.CONVERSATION: case ViewMode.AD: - closeSearchField(); mActionBar.setDisplayHomeAsUpEnabled(true); setEmptyMode(); break; @@ -257,17 +207,6 @@ public class ActionBarController implements ViewMode.ModeChangeListener, } } - /** - * Close the search query entry field to avoid keyboard events, and to restore the actionbar - * to non-search mode. - */ - private void closeSearchField() { - if (mSearch == null) { - return; - } - mSearch.collapseActionView(); - } - protected int getMode() { if (mViewModeController != null) { return mViewModeController.getMode(); @@ -382,71 +321,6 @@ public class ActionBarController implements ViewMode.ModeChangeListener, mActionBar.setHomeButtonEnabled(true); } - @Override - public boolean onQueryTextSubmit(String query) { - if (mSearch != null) { - MenuItemCompat.collapseActionView(mSearch); - mSearchWidget.setQuery("", false); - } - mController.executeSearch(query.trim()); - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - return false; - } - - // Next two methods are called when search suggestions are clicked. - @Override - public boolean onSuggestionSelect(int position) { - return onSuggestionClick(position); - } - - @Override - public boolean onSuggestionClick(int position) { - final Cursor c = mSearchWidget.getSuggestionsAdapter().getCursor(); - final boolean haveValidQuery = (c != null) && c.moveToPosition(position); - if (!haveValidQuery) { - LogUtils.d(LOG_TAG, "onSuggestionClick: Couldn't get a search query"); - // We haven't handled this query, but the default behavior will - // leave EXTRA_ACCOUNT un-populated, leading to a crash. So claim - // that we have handled the event. - return true; - } - collapseSearch(); - // what is in the text field - String queryText = mSearchWidget.getQuery().toString(); - // What the suggested query is - String query = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)); - // If the text the user typed in is a prefix of what is in the search - // widget suggestion query, just take the search widget suggestion - // query. Otherwise, it is a suffix and we want to remove matching - // prefix portions. - if (!TextUtils.isEmpty(queryText) && query.indexOf(queryText) != 0) { - final int queryTokenIndex = queryText - .lastIndexOf(SearchRecentSuggestionsProvider.QUERY_TOKEN_SEPARATOR); - if (queryTokenIndex > -1) { - queryText = queryText.substring(0, queryTokenIndex); - } - // Since we auto-complete on each token in a query, if the query the - // user typed up until the last token is a substring of the - // suggestion they click, make sure we don't double include the - // query text. For example: - // user types john, that matches john palo alto - // User types john p, that matches john john palo alto - // Remove the first john - // Only do this if we have multiple query tokens. - if (queryTokenIndex > -1 && !TextUtils.isEmpty(query) && query.contains(queryText) - && queryText.length() < query.length()) { - int start = query.indexOf(queryText); - query = query.substring(0, start) + query.substring(start + queryText.length()); - } - } - mController.executeSearch(query.trim()); - return true; - } - /** * Uses the current state to update the current folder {@link #mFolder} and the current * account {@link #mAccount} shown in the actionbar. Also updates the actionbar subtitle to @@ -487,36 +361,12 @@ public class ActionBarController implements ViewMode.ModeChangeListener, return; } /** True if we are changing folders. */ - final boolean changingFolders = (mFolder == null || !mFolder.equals(folder)); mFolder = folder; setFolderAndAccount(); - final ConversationListContext listContext = mController == null ? null : - mController.getCurrentListContext(); - if (changingFolders && !ConversationListContext.isSearchResult(listContext)) { - closeSearchField(); - } // make sure that we re-validate the optional menu items validateVolatileMenuOptionVisibility(); } - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - // Do nothing. Required as part of the interface, we ar only interested in - // onMenuItemActionCollapse(MenuItem). - // Have to return true here. Unlike other callbacks, the return value here is whether - // we want to suppress the action (rather than consume the action). We don't want to - // suppress the action. - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - // Have to return true here. Unlike other callbacks, the return value - // here is whether we want to suppress the action (rather than consume the action). We - // don't want to suppress the action. - return true; - } - /** * Sets the actionbar mode: Pass it an integer which contains each of these values, perhaps * OR'd together: {@link ActionBar#DISPLAY_SHOW_CUSTOM} and diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java index fec109b4b..1b0809e2d 100644 --- a/src/com/android/mail/ui/ActivityController.java +++ b/src/com/android/mail/ui/ActivityController.java @@ -337,4 +337,6 @@ public interface ActivityController extends LayoutListener, @LayoutRes int getContentViewResource(); View.OnClickListener getNavigationViewClickListener(); + + boolean isSearchBarShowing(); } diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java index b9995b947..c899d34dd 100644 --- a/src/com/android/mail/ui/ControllableActivity.java +++ b/src/com/android/mail/ui/ControllableActivity.java @@ -24,6 +24,7 @@ import com.android.mail.bitmap.ContactResolver; import com.android.mail.browse.ConversationListFooterView; import com.android.mail.providers.Account; import com.android.mail.providers.Folder; +import com.android.mail.providers.SearchRecentSuggestionsProvider; /** * A controllable activity is an Activity that has a Controller attached. This activity must be @@ -130,4 +131,6 @@ public interface ControllableActivity extends RestrictedActivity, * Shows help to user, could be in browser or another activity. */ void showHelp(Account account, int viewMode); + + SearchRecentSuggestionsProvider getSuggestionsProvider(); } diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java index 77e286861..dd0fda701 100644 --- a/src/com/android/mail/ui/ConversationListFragment.java +++ b/src/com/android/mail/ui/ConversationListFragment.java @@ -722,7 +722,6 @@ public final class ConversationListFragment extends Fragment implements // Set default navigation if (ViewMode.isListMode(newMode)) { mListView.setNextFocusRightId(R.id.conversation_list_view); - mListView.requestFocus(); } else if (ViewMode.isConversationMode(newMode)) { // This would only happen in two_pane mListView.setNextFocusRightId(R.id.conversation_pager); diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java index 96b06496f..045311449 100644 --- a/src/com/android/mail/ui/FolderSelectionActivity.java +++ b/src/com/android/mail/ui/FolderSelectionActivity.java @@ -39,6 +39,7 @@ import com.android.mail.bitmap.ContactResolver; import com.android.mail.providers.Account; import com.android.mail.providers.Folder; import com.android.mail.providers.FolderWatcher; +import com.android.mail.providers.SearchRecentSuggestionsProvider; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; import com.android.mail.utils.MailObservable; @@ -485,6 +486,12 @@ public class FolderSelectionActivity extends ActionBarActivity implements OnClic @Override public void showHelp(Account account, int viewMode) { - //Unsupported + // Unsupported + } + + @Override + public SearchRecentSuggestionsProvider getSuggestionsProvider() { + // Unsupported; + return null; } } diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java index 78507dad1..bd640c563 100644 --- a/src/com/android/mail/ui/MailActivity.java +++ b/src/com/android/mail/ui/MailActivity.java @@ -45,6 +45,8 @@ import com.android.mail.bitmap.ContactResolver; import com.android.mail.compose.ComposeActivity; import com.android.mail.providers.Account; import com.android.mail.providers.Folder; +import com.android.mail.providers.SearchRecentSuggestionsProvider; +import com.android.mail.providers.SuggestionsProvider; import com.android.mail.utils.StorageLowState; import com.android.mail.utils.Utils; @@ -497,6 +499,11 @@ public class MailActivity extends AbstractMailActivity implements ControllableAc Utils.showHelp(this, account, getString(helpContext)); } + @Override + public SearchRecentSuggestionsProvider getSuggestionsProvider() { + return new SuggestionsProvider(this); + } + /** * Returns the loader callback that can create a * {@link AbstractActivityController#LOADER_WELCOME_TOUR_ACCOUNTS} followed by a diff --git a/src/com/android/mail/ui/MaterialSearchActionView.java b/src/com/android/mail/ui/MaterialSearchActionView.java new file mode 100644 index 000000000..80e301600 --- /dev/null +++ b/src/com/android/mail/ui/MaterialSearchActionView.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 Google Inc. + * Licensed to 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 com.android.mail.ui; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.mail.R; + +/** + * Custom view for the action bar when search is displayed. + */ +public class MaterialSearchActionView extends LinearLayout implements TextWatcher, + View.OnClickListener, TextView.OnEditorActionListener { + private MaterialSearchViewController mController; + private InputMethodManager mImm; + private boolean mShowingClose; + private boolean mSupportVoice; + + private View mBackButton; + private EditText mQueryText; + private ImageView mEndingButton; + + public MaterialSearchActionView(Context context) { + super(context); + } + + public MaterialSearchActionView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + // PUBLIC API + public void setController(MaterialSearchViewController controller, String initialQuery, + boolean supportVoice) { + mController = controller; + mQueryText.setText(initialQuery); + mSupportVoice = supportVoice; + } + + public void clearSearchQuery() { + mQueryText.setText(""); + } + + public void focusSearchBar(boolean hasFocus) { + mQueryText.requestFocus(); + if (hasFocus) { + mImm.showSoftInput(mQueryText, 0); + } else { + mImm.hideSoftInputFromWindow(mQueryText.getWindowToken(), 0); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + mBackButton = findViewById(R.id.search_actionbar_back_button); + mBackButton.setOnClickListener(this); + mQueryText = (EditText) findViewById(R.id.search_actionbar_query_text); + mQueryText.addTextChangedListener(this); + mQueryText.setOnClickListener(this); + mQueryText.setOnEditorActionListener(this); + mEndingButton = (ImageView) findViewById(R.id.search_actionbar_ending_button); + mEndingButton.setOnClickListener(this); + } + + @Override + public void setVisibility(int visibility) { + if (visibility != VISIBLE) { + mQueryText.setText(""); + } + super.setVisibility(visibility); + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + // Only care about onTextChanged + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + mController.onQueryTextChanged(charSequence.toString()); + if (!mSupportVoice || charSequence.length() > 0) { + mShowingClose = true; + mEndingButton.setImageResource(R.drawable.ic_close_24dp); + } else { + mShowingClose = false; + mEndingButton.setImageResource(R.drawable.ic_mic_24dp); + } + } + + @Override + public void afterTextChanged(Editable editable) { + // Only care about onTextChanged + } + + @Override + public void onClick(View view) { + if (view == mBackButton) { + mController.onSearchCanceled(); + mQueryText.setText(""); + } else if (view == mEndingButton) { + if (mShowingClose) { + mQueryText.setText(""); + mController.showSearchActionBar( + MaterialSearchViewController.SEARCH_VIEW_STATE_VISIBLE); + } else { + mController.onVoiceSearch(); + } + } else if (view == mQueryText) { + mController.showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_VISIBLE); + } + } + + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + mController.onSearchPerformed(mQueryText.getText().toString()); + } + return false; + } +} diff --git a/src/com/android/mail/ui/MaterialSearchSuggestionsList.java b/src/com/android/mail/ui/MaterialSearchSuggestionsList.java new file mode 100644 index 000000000..30d469687 --- /dev/null +++ b/src/com/android/mail/ui/MaterialSearchSuggestionsList.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2014 Google Inc. + * Licensed to 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 com.android.mail.ui; + +import android.app.SearchManager; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.mail.R; +import com.android.mail.providers.SearchRecentSuggestionsProvider; +import com.google.common.collect.Lists; + +import java.util.List; + +/** + * Custom quantum-styled search view that overlays the main activity. + */ +public class MaterialSearchSuggestionsList extends LinearLayout + implements AdapterView.OnItemClickListener, View.OnClickListener { + private MaterialSearchViewController mController; + private SearchRecentSuggestionsProvider mSuggestionsProvider; + private List mSuggestions = Lists.newArrayList(); + private String mQuery; + + private View mDummyHolder; + private ListView mListView; + private QuantumSearchViewListAdapter mAdapter; + private QuerySuggestionsTask mQueryTask; + + public MaterialSearchSuggestionsList(Context context) { + super(context); + } + + public MaterialSearchSuggestionsList(Context context, AttributeSet attrs) { + super(context, attrs); + } + + // PUBLIC API + public void setController(MaterialSearchViewController controller, + SearchRecentSuggestionsProvider suggestionsProvider) { + mController = controller; + mSuggestionsProvider = suggestionsProvider; + } + + public void setQuery(String query) { + mQuery = query; + if (mQueryTask != null) { + mQueryTask.cancel(true); + } + mQueryTask = new QuerySuggestionsTask(); + mQueryTask.execute(query); + } + + // PRIVATE API + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mListView = (ListView) findViewById(R.id.search_overlay_suggestion_list); + mListView.setOnItemClickListener(this); + mDummyHolder = findViewById(R.id.search_overlay_scrim); + mDummyHolder.setOnClickListener(this); + + // set up the adapter + mAdapter = new QuantumSearchViewListAdapter(getContext(), R.layout.search_suggestion_item); + mListView.setAdapter(mAdapter); + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + mController.onSearchPerformed(mSuggestions.get(position).suggestion); + } + + @Override + public void onClick(View view) { + mController.showSearchActionBar( + MaterialSearchViewController.SEARCH_VIEW_STATE_ONLY_ACTIONBAR); + } + + // Background task for querying the suggestions list + private class QuerySuggestionsTask extends AsyncTask> { + @Override + protected List doInBackground(String... strings) { + String query = strings[0]; + if (query == null) { + query = ""; + } + + Cursor c = null; + final List result = Lists.newArrayList(); + try { + c = mSuggestionsProvider.query(null, "query LIKE ?", + new String[] { query }, null); + + if (c != null && c.moveToFirst()) { + final int textIndex = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY); + final int iconIndex = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + do { + final String suggestion = c.getString(textIndex); + final Uri iconUri = Uri.parse(c.getString(iconIndex)); + result.add(new SuggestionItem(suggestion, iconUri)); + } while (c.moveToNext()); + } + } finally { + if (c != null) { + c.close(); + } + } + + return result; + } + + @Override + protected void onPostExecute(List strings) { + if (!isCancelled()) { + // Should not have any race conditions here since we cancel the previous asynctask + // before starting the new one. It's unlikely that the new task finishes fast enough + // to get to onPostExecute when this one is in addAll. + mSuggestions.clear(); + mSuggestions.addAll(strings); + mAdapter.notifyDataSetChanged(); + } + } + } + + private static class SuggestionItem { + final String suggestion; + final Uri icon; + + public SuggestionItem(String s, Uri i) { + suggestion = s; + icon = i; + } + } + + // Custom adapter to populate our list + private class QuantumSearchViewListAdapter extends BaseAdapter { + private final Context mContext; + private final int mResId; + private LayoutInflater mInflater; + + public QuantumSearchViewListAdapter(Context context, int resource) { + super(); + mContext = context; + mResId = resource; + } + + private LayoutInflater getInflater() { + if (mInflater == null) { + mInflater = LayoutInflater.from(mContext); + } + return mInflater; + } + + @Override + public int getCount() { + return mSuggestions.size(); + } + + @Override + public Object getItem(int i) { + return mSuggestions.get(i); + } + + @Override + public long getItemId(int i) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getInflater().inflate(mResId, parent, false); + } + + final SuggestionItem item = mSuggestions.get(position); + ((TextView) convertView.findViewById(R.id.search_overlay_item_text)) + .setText(item.suggestion); + ((ImageView) convertView.findViewById(R.id.search_overlay_item_icon)) + .setImageURI(item.icon); + + return convertView; + } + } +} diff --git a/src/com/android/mail/ui/MaterialSearchViewController.java b/src/com/android/mail/ui/MaterialSearchViewController.java new file mode 100644 index 000000000..d382754b7 --- /dev/null +++ b/src/com/android/mail/ui/MaterialSearchViewController.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2014 Google Inc. + * Licensed to 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 com.android.mail.ui; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.speech.RecognizerIntent; +import android.view.View; +import android.widget.Toast; + +import com.android.mail.ConversationListContext; +import com.android.mail.R; +import com.android.mail.providers.SearchRecentSuggestionsProvider; + +import java.util.Locale; + +/** + * Controller for interactions between ActivityController and our custom search views. + */ +public class MaterialSearchViewController implements ViewMode.ModeChangeListener { + // The controller is not in search mode. Both search action bar and the suggestion list + // are not visible to the user. + public static final int SEARCH_VIEW_STATE_GONE = 0; + // The controller is actively in search (as in the action bar is focused and the user can type + // into the search query). Both the search action bar and the suggestion list are visible. + public static final int SEARCH_VIEW_STATE_VISIBLE = 1; + // The controller is in a search ViewMode but not actively searching. This is relevant when + // we have to show the search actionbar on top while the user is not interacting with it. + public static final int SEARCH_VIEW_STATE_ONLY_ACTIONBAR = 2; + + /** Code returned from voice search intent */ + public static final int VOICE_SEARCH_REQUEST_CODE = 4; + + private static final String EXTRA_VIEW_STATE = "extraSearchViewControllerViewState"; + + private final MailActivity mActivity; + private final ActivityController mController; + + protected SearchRecentSuggestionsProvider mSuggestionsProvider; + protected View mSearchActionViewShadow; + protected MaterialSearchActionView mSearchActionView; + protected MaterialSearchSuggestionsList mSearchSuggestionList; + + private int mViewMode; + private int mViewState; + + public MaterialSearchViewController(MailActivity activity, ActivityController controller, + Intent intent, Bundle savedInstanceState) { + mActivity = activity; + mController = controller; + + final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + final boolean supportVoice = + voiceIntent.resolveActivity(mActivity.getPackageManager()) != null; + + mSuggestionsProvider = mActivity.getSuggestionsProvider(); + mSearchSuggestionList = (MaterialSearchSuggestionsList) mActivity.findViewById( + R.id.search_overlay_view); + mSearchSuggestionList.setController(this, mSuggestionsProvider); + mSearchActionView = (MaterialSearchActionView) mActivity.findViewById( + R.id.search_actionbar_view); + mSearchActionView.setController(this, intent.getStringExtra( + ConversationListContext.EXTRA_SEARCH_QUERY), supportVoice); + mSearchActionViewShadow = mActivity.findViewById(R.id.search_actionbar_shadow); + + if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_VIEW_STATE)) { + mViewState = savedInstanceState.getInt(EXTRA_VIEW_STATE); + } + + mActivity.getViewMode().addListener(this); + } + + public void onDestroy() { + mSuggestionsProvider.cleanup(); + mSuggestionsProvider = null; + mActivity.getViewMode().removeListener(this); + } + + public void saveState(Bundle outState) { + outState.putInt(EXTRA_VIEW_STATE, mViewState); + } + + @Override + public void onViewModeChanged(int newMode) { + if (mController.isSearchBarShowing()) { + showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_ONLY_ACTIONBAR); + } else if (mViewMode == 0) { + showSearchActionBar(mViewState); + } else { + showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_GONE); + } + mViewMode = newMode; + } + + public boolean handleBackPress() { + if (mController.isSearchBarShowing() && mSearchSuggestionList.isShown()) { + showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_ONLY_ACTIONBAR); + return true; + } else if (mSearchActionView.isShown()) { + showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_GONE); + return true; + } + return false; + } + + // Should use the view states specified in MaterialSearchViewController + public void showSearchActionBar(int state) { + mViewState = state; + switch (state) { + case MaterialSearchViewController.SEARCH_VIEW_STATE_ONLY_ACTIONBAR: + // Only actionbar is only applicable in search mode + if (mController.isSearchBarShowing()) { + mSearchActionView.setVisibility(View.VISIBLE); + mSearchActionViewShadow.setVisibility(View.VISIBLE); + mSearchSuggestionList.setVisibility(View.GONE); + mSearchActionView.focusSearchBar(false); + break; + } + // Fallthrough to setting everything invisible + case MaterialSearchViewController.SEARCH_VIEW_STATE_GONE: + mSearchActionView.setVisibility(View.GONE); + mSearchActionViewShadow.setVisibility(View.GONE); + mSearchSuggestionList.setVisibility(View.GONE); + mSearchActionView.focusSearchBar(false); + break; + case MaterialSearchViewController.SEARCH_VIEW_STATE_VISIBLE: + mSearchActionView.setVisibility(View.VISIBLE); + mSearchActionViewShadow.setVisibility(View.VISIBLE); + mSearchSuggestionList.setVisibility(View.VISIBLE); + mSearchActionView.focusSearchBar(true); + break; + } + } + + public void onQueryTextChanged(String query) { + mSearchSuggestionList.setQuery(query); + } + + public void onSearchCanceled() { + // Special case search mode + if (mActivity.getViewMode().isSearchMode()) { + mActivity.setResult(Activity.RESULT_OK); + mActivity.finish(); + } else { + showSearchActionBar(MaterialSearchViewController.SEARCH_VIEW_STATE_GONE); + } + } + + public void onSearchPerformed(String query) { + mSearchActionView.clearSearchQuery(); + mController.executeSearch(query); + } + + public void onVoiceSearch() { + final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().getLanguage()); + + // Some devices do not support the voice-to-speech functionality. + try { + mActivity.startActivityForResult(intent, VOICE_SEARCH_REQUEST_CODE); + } catch (ActivityNotFoundException e) { + final String toast = + mActivity.getResources().getString(R.string.voice_search_not_supported); + Toast.makeText(mActivity, toast, Toast.LENGTH_LONG).show(); + } + } + + public void saveRecentQuery(final String query) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + mSuggestionsProvider.saveRecentQuery(query); + return null; + } + }.execute(); + } +} diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java index 0d0e58dec..1030b0c11 100644 --- a/src/com/android/mail/ui/OnePaneController.java +++ b/src/com/android/mail/ui/OnePaneController.java @@ -174,6 +174,7 @@ public final class OnePaneController extends AbstractActivityController { if (ViewMode.isListMode(newMode)) { mPagerController.hide(true /* changeVisibility */); } + // When we step away from the conversation mode, we don't have a current conversation // anymore. Let's blank it out so clients calling getCurrentConversation are not misled. if (!ViewMode.isConversationMode(newMode)) { @@ -513,4 +514,9 @@ public final class OnePaneController extends AbstractActivityController { public boolean isTwoPaneLandscape() { return false; } + + @Override + public boolean isSearchBarShowing() { + return mViewMode.getMode() == ViewMode.SEARCH_RESULTS_LIST; + } } diff --git a/src/com/android/mail/ui/SearchActionBarController.java b/src/com/android/mail/ui/SearchActionBarController.java deleted file mode 100644 index 97f1fbedc..000000000 --- a/src/com/android/mail/ui/SearchActionBarController.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * Licensed to 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 com.android.mail.ui; - -import android.content.Context; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.mail.ConversationListContext; -import com.android.mail.utils.Utils; - -/** - * This class is used to control the actionbar for the search activity. - * It shows/hides various menu items based on the viewmode. - */ -public class SearchActionBarController extends ActionBarController { - - public SearchActionBarController(Context context) { - super(context); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - switch (getMode()) { - case ViewMode.SEARCH_RESULTS_LIST: - setSearchQueryTerm(); - mActionBar.setDisplayHomeAsUpEnabled(true); - // And immediately give up focus to avoid keyboard popping and suggestions. - clearSearchFocus(); - break; - case ViewMode.SEARCH_RESULTS_CONVERSATION: - if (mIsOnTablet) { - setSearchQueryTerm(); - } - mActionBar.setDisplayHomeAsUpEnabled(true); - // And immediately give up focus to avoid keyboard popping and suggestions. - clearSearchFocus(); - break; - } - return false; - } - - @Override - public void onViewModeChanged(int newMode) { - super.onViewModeChanged(newMode); - switch (getMode()) { - case ViewMode.SEARCH_RESULTS_LIST: - setEmptyMode(); - break; - } - } - - /** - * Remove focus from the search field to avoid - * 1. The keyboard popping in and out. - * 2. The search suggestions shown up. - */ - private void clearSearchFocus() { - // Remove focus from the search action menu in search results mode so - // the IME and the suggestions don't get in the way. - final MenuItem search = getSearch(); - if (search != null) { - final SearchView searchWidget = (SearchView) MenuItemCompat.getActionView(search); - searchWidget.clearFocus(); - } - } - - /** - * Sets the query term in the text field, so the user can see what was searched for. - */ - private void setSearchQueryTerm() { - final MenuItem search = getSearch(); - if (search != null) { - MenuItemCompat.expandActionView(search); - final String query = mActivity.getIntent().getStringExtra( - ConversationListContext.EXTRA_SEARCH_QUERY); - final SearchView searchWidget = (SearchView) MenuItemCompat.getActionView(search); - if (!TextUtils.isEmpty(query)) { - searchWidget.setQuery(query, false); - } - } - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - // When we are in the search activity, back closes the search action mode. At that point - // we want to quit the activity entirely. - final int mode = getMode(); - if (mode == ViewMode.SEARCH_RESULTS_LIST - || (Utils.showTwoPaneSearchResults(getContext()) - && mode == ViewMode.SEARCH_RESULTS_CONVERSATION)) { - - // When the action menu is collapsed, the search activity has finished. We should exit - // search at this point - mController.exitSearchMode(); - } - // The return value here is whether we want to collapse the action mode. Since we want to - // collapse the action mode, we should return true. - return true; - } -} diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java index 3be51ff22..bb9b4fa33 100644 --- a/src/com/android/mail/ui/TwoPaneController.java +++ b/src/com/android/mail/ui/TwoPaneController.java @@ -153,7 +153,7 @@ public final class TwoPaneController extends AbstractActivityController implemen @Override public boolean onCreate(Bundle savedState) { mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity); - if (mLayout == null) { + if (mLayout == null) { // We need the layout for everything. Crash/Return early if it is null. LogUtils.wtf(LOG_TAG, "mLayout is null!"); return false; @@ -632,4 +632,11 @@ public final class TwoPaneController extends AbstractActivityController implemen public boolean isTwoPaneLandscape() { return mIsTabletLandscape; } + + @Override + public boolean isSearchBarShowing() { + final int mode = mViewMode.getMode(); + return mode == ViewMode.SEARCH_RESULTS_LIST || + (mIsTabletLandscape && mode == ViewMode.SEARCH_RESULTS_CONVERSATION); + } } diff --git a/src/com/android/mail/ui/settings/GeneralPrefsFragment.java b/src/com/android/mail/ui/settings/GeneralPrefsFragment.java index b33ec6db7..a5e41c91e 100644 --- a/src/com/android/mail/ui/settings/GeneralPrefsFragment.java +++ b/src/com/android/mail/ui/settings/GeneralPrefsFragment.java @@ -27,7 +27,6 @@ import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; -import android.provider.SearchRecentSuggestions; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -167,12 +166,10 @@ public class GeneralPrefsFragment extends MailPreferenceFragment new AsyncTask() { @Override protected Void doInBackground(Void... params) { - final String authority = context.getString( - com.android.mail.R.string.suggestions_authority); - final SearchRecentSuggestions suggestions = - new SearchRecentSuggestions(context, authority, - SuggestionsProvider.MODE); + final SuggestionsProvider suggestions = + new SuggestionsProvider(context); suggestions.clearHistory(); + suggestions.cleanup(); return null; } }.execute(); diff --git a/unified_src/com/android/mail/providers/UnifiedAccountCacheProvider.java b/unified_src/com/android/mail/providers/UnifiedAccountCacheProvider.java index b1c131362..57f1fe8f3 100644 --- a/unified_src/com/android/mail/providers/UnifiedAccountCacheProvider.java +++ b/unified_src/com/android/mail/providers/UnifiedAccountCacheProvider.java @@ -24,11 +24,6 @@ public class UnifiedAccountCacheProvider extends MailAppProvider { // The authority of our conversation provider (a forwarding provider) // This string must match the declaration in AndroidManifest.xml private static final String sAuthority = "com.android.mail.accountcache"; - /** - * Authority for the suggestions provider. This is specified in AndroidManifest.xml and - * res/xml/searchable.xml. - */ - private static final String sSuggestionsAuthority = "com.android.mail.suggestionsprovider"; @Override protected String getAuthority() { @@ -36,12 +31,13 @@ public class UnifiedAccountCacheProvider extends MailAppProvider { } @Override - protected Intent getNoAccountsIntent(Context context) { + public String getSuggestionAuthority() { + // UnifiedEmail does not use the default search. return null; } @Override - public String getSuggestionAuthority() { - return sSuggestionsAuthority; + protected Intent getNoAccountsIntent(Context context) { + return null; } } -- cgit v1.2.3