diff options
61 files changed, 951 insertions, 572 deletions
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"> <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> @@ -58,7 +59,6 @@ <action android:name="android.intent.action.SEARCH" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> - <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity> <activity android:name=".compose.ComposeActivity" android:theme="@style/ComposeTheme"> @@ -159,13 +159,6 @@ <grant-uri-permission android:pathPattern=".*" /> </provider> - <!-- The android:name is the name of the Provider class which is stored in - UnifiedEmail, and has package name com.android.mail.providers and the class is - called SuggestionsProvider. The authority name is specified in the MailAppProvider - which is specific to the two apps separately. --> - <provider android:name="com.android.mail.providers.SuggestionsProvider" - android:authorities="com.android.mail.suggestionsprovider" /> - <service android:name=".compose.EmptyService"/> <!-- Widget --> diff --git a/res/drawable-hdpi/ic_arrow_back_24dp.png b/res/drawable-hdpi/ic_arrow_back_24dp.png Binary files differnew file mode 100644 index 000000000..9b5f436f6 --- /dev/null +++ b/res/drawable-hdpi/ic_arrow_back_24dp.png diff --git a/res/drawable-hdpi/ic_close_24dp.png b/res/drawable-hdpi/ic_close_24dp.png Binary files differnew file mode 100644 index 000000000..33db9eef6 --- /dev/null +++ b/res/drawable-hdpi/ic_close_24dp.png diff --git a/res/drawable-hdpi/ic_history_24dp.png b/res/drawable-hdpi/ic_history_24dp.png Binary files differnew file mode 100644 index 000000000..b93e92940 --- /dev/null +++ b/res/drawable-hdpi/ic_history_24dp.png diff --git a/res/drawable-hdpi/ic_history_holo_light.png b/res/drawable-hdpi/ic_history_holo_light.png Binary files differdeleted file mode 100644 index d3feeac20..000000000 --- a/res/drawable-hdpi/ic_history_holo_light.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_mic_24dp.png b/res/drawable-hdpi/ic_mic_24dp.png Binary files differnew file mode 100644 index 000000000..143ccc43a --- /dev/null +++ b/res/drawable-hdpi/ic_mic_24dp.png diff --git a/res/drawable-mdpi/ic_arrow_back_24dp.png b/res/drawable-mdpi/ic_arrow_back_24dp.png Binary files differnew file mode 100644 index 000000000..3a5800d1e --- /dev/null +++ b/res/drawable-mdpi/ic_arrow_back_24dp.png diff --git a/res/drawable-mdpi/ic_close_24dp.png b/res/drawable-mdpi/ic_close_24dp.png Binary files differnew file mode 100644 index 000000000..bd39f9332 --- /dev/null +++ b/res/drawable-mdpi/ic_close_24dp.png diff --git a/res/drawable-mdpi/ic_history_24dp.png b/res/drawable-mdpi/ic_history_24dp.png Binary files differnew file mode 100644 index 000000000..76b0a9178 --- /dev/null +++ b/res/drawable-mdpi/ic_history_24dp.png diff --git a/res/drawable-mdpi/ic_history_holo_light.png b/res/drawable-mdpi/ic_history_holo_light.png Binary files differdeleted file mode 100644 index 2b6eec4f5..000000000 --- a/res/drawable-mdpi/ic_history_holo_light.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_mic_24dp.png b/res/drawable-mdpi/ic_mic_24dp.png Binary files differnew file mode 100644 index 000000000..81d0c21ad --- /dev/null +++ b/res/drawable-mdpi/ic_mic_24dp.png diff --git a/res/drawable-xhdpi/ic_arrow_back_24dp.png b/res/drawable-xhdpi/ic_arrow_back_24dp.png Binary files differnew file mode 100644 index 000000000..73395b0ac --- /dev/null +++ b/res/drawable-xhdpi/ic_arrow_back_24dp.png diff --git a/res/drawable-xhdpi/ic_close_24dp.png b/res/drawable-xhdpi/ic_close_24dp.png Binary files differnew file mode 100644 index 000000000..81efb7040 --- /dev/null +++ b/res/drawable-xhdpi/ic_close_24dp.png diff --git a/res/drawable-xhdpi/ic_history_24dp.png b/res/drawable-xhdpi/ic_history_24dp.png Binary files differnew file mode 100644 index 000000000..61c0bfe34 --- /dev/null +++ b/res/drawable-xhdpi/ic_history_24dp.png diff --git a/res/drawable-xhdpi/ic_history_holo_light.png b/res/drawable-xhdpi/ic_history_holo_light.png Binary files differdeleted file mode 100644 index 83d61fa68..000000000 --- a/res/drawable-xhdpi/ic_history_holo_light.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_mic_24dp.png b/res/drawable-xhdpi/ic_mic_24dp.png Binary files differnew file mode 100644 index 000000000..1dcf56c4e --- /dev/null +++ b/res/drawable-xhdpi/ic_mic_24dp.png diff --git a/res/drawable-xxhdpi/ic_arrow_back_24dp.png b/res/drawable-xxhdpi/ic_arrow_back_24dp.png Binary files differnew file mode 100644 index 000000000..650351d06 --- /dev/null +++ b/res/drawable-xxhdpi/ic_arrow_back_24dp.png diff --git a/res/drawable-xxhdpi/ic_close_24dp.png b/res/drawable-xxhdpi/ic_close_24dp.png Binary files differnew file mode 100644 index 000000000..0924e29a2 --- /dev/null +++ b/res/drawable-xxhdpi/ic_close_24dp.png diff --git a/res/drawable-xxhdpi/ic_history_24dp.png b/res/drawable-xxhdpi/ic_history_24dp.png Binary files differnew file mode 100644 index 000000000..4fe0a7efe --- /dev/null +++ b/res/drawable-xxhdpi/ic_history_24dp.png diff --git a/res/drawable-xxhdpi/ic_history_holo_light.png b/res/drawable-xxhdpi/ic_history_holo_light.png Binary files differdeleted file mode 100644 index b5e82bc69..000000000 --- a/res/drawable-xxhdpi/ic_history_holo_light.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_mic_24dp.png b/res/drawable-xxhdpi/ic_mic_24dp.png Binary files differnew file mode 100644 index 000000000..ce9890478 --- /dev/null +++ b/res/drawable-xxhdpi/ic_mic_24dp.png diff --git a/res/drawable-xxxhdpi/ic_arrow_back_24dp.png b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png Binary files differnew file mode 100644 index 000000000..8068c1b57 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png diff --git a/res/drawable-xxxhdpi/ic_close_24dp.png b/res/drawable-xxxhdpi/ic_close_24dp.png Binary files differnew file mode 100644 index 000000000..cb7a27ca8 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_close_24dp.png diff --git a/res/drawable-xxxhdpi/ic_history_24dp.png b/res/drawable-xxxhdpi/ic_history_24dp.png Binary files differnew file mode 100644 index 000000000..420576b08 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_history_24dp.png diff --git a/res/drawable-xxxhdpi/ic_mic_24dp.png b/res/drawable-xxxhdpi/ic_mic_24dp.png Binary files differnew file mode 100644 index 000000000..c586bb535 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_mic_24dp.png diff --git a/res/values-sw600dp-land/dimen.xml b/res/drawable/action_bar_shadow.xml index 92359cbf0..32501171d 100644 --- a/res/values-sw600dp-land/dimen.xml +++ b/res/drawable/action_bar_shadow.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012 Google Inc. + Copyright (C) 2014 Google Inc. Licensed to The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<resources> - <dimen name="search_view_width">500dip</dimen> -</resources> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:angle="270" + android:startColor="#3D000000" + android:endColor="#00000000" /> +</shape> 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 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2011 Google Inc. + Copyright (C) 2014 Google Inc. Licensed to The Android Open Source Project. Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,44 @@ limitations under the License. --> -<android.support.v7.widget.SearchView +<com.android.mail.ui.MaterialSearchActionView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/search_layout" - android:gravity="center_vertical" - android:layout_height="match_parent" + android:id="@+id/search_actionbar_view" android:layout_width="match_parent" - android:hint="@string/search_hint" - android:imeOptions="actionSearch" - android:maxWidth="@dimen/search_view_width" /> + android:layout_height="match_parent" + android:background="@android:color/white"> + + <ImageView + android:id="@+id/search_actionbar_back_button" + android:layout_width="@dimen/search_leading_button_width" + android:layout_height="match_parent" + android:background="?android:attr/selectableItemBackground" + android:src="@drawable/ic_arrow_back_24dp" + style="@style/SearchViewLeadingButton" /> + + <EditText + android:id="@+id/search_actionbar_query_text" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="@android:color/transparent" + android:gravity="center_vertical" + android:hint="@string/search_hint" + android:imeOptions="actionSearch|flagNoExtractUi" + android:inputType="text|textNoSuggestions" + android:singleLine="true" + android:textColor="@color/search_query_text" + android:textColorHint="@color/search_query_hint_text" + android:textSize="16sp" /> + + <ImageView + android:id="@+id/search_actionbar_ending_button" + android:layout_width="@dimen/search_ending_button_width" + android:layout_height="match_parent" + android:background="?android:attr/selectableItemBackground" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:scaleType="center" + android:src="@drawable/ic_mic_24dp" /> + +</com.android.mail.ui.MaterialSearchActionView>
\ 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 @@ --> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_container" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -27,12 +26,8 @@ android:layout_height="match_parent" android:orientation="vertical"> - <android.support.v7.widget.Toolbar - android:id="@+id/mail_toolbar" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - app:theme="?attr/actionBarTheme" /> + <!-- Custom toolbar/search overlay --> + <include layout="@layout/toolbar_with_search" /> <FrameLayout android:layout_width="match_parent" @@ -47,6 +42,8 @@ <include layout="@layout/floating_actions" /> + <include layout="@layout/search_suggestion_list" /> + </FrameLayout> </LinearLayout> @@ -57,4 +54,4 @@ android:layout_height="match_parent" android:layout_gravity="start" /> -</android.support.v4.widget.DrawerLayout> +</android.support.v4.widget.DrawerLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/search_suggestion_height" + android:orientation="vertical"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:paddingRight="16dp"> + + <ImageView + android:id="@+id/search_overlay_item_icon" + android:layout_width="@dimen/search_leading_button_width" + android:layout_height="match_parent" + style="@style/SearchViewLeadingButton" /> + + <TextView + android:id="@+id/search_overlay_item_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:textColor="@color/search_suggestion_item_text" + android:textSize="16sp" /> + + </LinearLayout> + +</LinearLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.android.mail.ui.MaterialSearchSuggestionsList + android:id="@+id/search_overlay_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:visibility="gone"> + + <ListView + android:id="@+id/search_overlay_suggestion_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/white" + android:divider="@null" /> + + <!-- Scrim to fade the background --> + <View + android:id="@+id/search_overlay_scrim" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:background="#88000000" /> + + </com.android.mail.ui.MaterialSearchSuggestionsList> + + <!-- Search bar shadow --> + <View + android:id="@+id/search_actionbar_shadow" + android:layout_width="match_parent" + android:layout_height="7dp" + android:background="@drawable/action_bar_shadow" + android:visibility="gone" /> +</FrameLayout>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize"> + + <android.support.v7.widget.Toolbar + android:id="@+id/mail_toolbar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorPrimary" + app:theme="?attr/actionBarTheme" /> + + <include layout="@layout/mail_actionbar_searchview" /> + +</FrameLayout>
\ 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. --> -<com.android.mail.ui.TwoPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/two_pane_activity" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/tablet_background_gray"> + android:orientation="vertical"> + <!-- Custom toolbar/search overlay --> + <include layout="@layout/toolbar_with_search" /> + + <!-- Main content --> <FrameLayout - android:id="@+id/drawer" - android:layout_width="@dimen/two_pane_drawer_width_open" - android:layout_height="match_parent" - android:layout_gravity="left"> + android:layout_width="match_parent" + android:layout_height="match_parent"> - <include layout="@layout/drawer_fragment" + <com.android.mail.ui.TwoPaneLayout + android:id="@+id/two_pane_activity" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:background="@color/tablet_background_gray"> - </FrameLayout> + <FrameLayout + android:id="@+id/drawer" + android:layout_width="@dimen/two_pane_drawer_width_open" + android:layout_height="match_parent" + android:layout_gravity="left"> - <FrameLayout - android:id="@+id/conversation_list_pane" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_gravity="left" - style="@style/TwoPaneConversationList" /> - - <com.android.mail.ui.ConversationViewFrame - android:id="@+id/conversation_pane" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_gravity="left"> - - <include layout="@layout/conversation_pager" - android:layout_width="match_parent" - android:layout_height="match_parent" /> + <include layout="@layout/drawer_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/conversation_list_pane" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="left" + style="@style/TwoPaneConversationList" /> - </com.android.mail.ui.ConversationViewFrame> + <com.android.mail.ui.ConversationViewFrame + android:id="@+id/conversation_pane" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="left"> - <com.android.mail.ui.ConversationViewFrame - android:id="@+id/miscellaneous_pane" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_gravity="left" /> + <include layout="@layout/conversation_pager" + android:layout_width="match_parent" + android:layout_height="match_parent" /> - <include layout="@layout/floating_actions" /> + </com.android.mail.ui.ConversationViewFrame> + + <com.android.mail.ui.ConversationViewFrame + android:id="@+id/miscellaneous_pane" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="left" /> + + <include layout="@layout/floating_actions" /> + + </com.android.mail.ui.TwoPaneLayout> + + <include layout="@layout/search_suggestion_list" /> + + </FrameLayout> -</com.android.mail.ui.TwoPaneLayout> +</LinearLayout>
\ 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 @@ <item android:id="@+id/search" android:title="@string/menu_search" app:showAsAction="always|collapseActionView" - android:icon="@drawable/ic_menu_search" - app:actionLayout="@layout/mail_actionbar_searchview" /> + android:icon="@drawable/ic_menu_search" /> <item android:id="@+id/archive" diff --git a/res/menu-sw600dp-land/conversation_list_search_results_actions.xml b/res/menu-sw600dp-land/conversation_list_search_results_actions.xml index 1b784bc56..1e465722d 100644 --- a/res/menu-sw600dp-land/conversation_list_search_results_actions.xml +++ b/res/menu-sw600dp-land/conversation_list_search_results_actions.xml @@ -21,8 +21,7 @@ <item android:id="@+id/search" android:title="@string/menu_search" app:showAsAction="always|collapseActionView" - android:icon="@drawable/ic_menu_search" - app:actionLayout="@layout/mail_actionbar_searchview" /> + android:icon="@drawable/ic_menu_search" /> <item android:id="@+id/delete" diff --git a/res/menu/conversation_list_menu.xml b/res/menu/conversation_list_menu.xml index 05568bc0d..8b7c59377 100644 --- a/res/menu/conversation_list_menu.xml +++ b/res/menu/conversation_list_menu.xml @@ -35,8 +35,7 @@ that supports FOLDER_SERVER_SEARCH --> <item android:id="@+id/search" android:title="@string/menu_search" app:showAsAction="ifRoom|collapseActionView" - android:icon="@drawable/ic_menu_search" - app:actionLayout="@layout/mail_actionbar_searchview" /> + android:icon="@drawable/ic_menu_search" /> <!-- These invisible menu item are used to enable keyboard shortcuts --> <item diff --git a/res/menu/conversation_list_search_results_actions.xml b/res/menu/conversation_list_search_results_actions.xml index 6708c8366..1205e9066 100644 --- a/res/menu/conversation_list_search_results_actions.xml +++ b/res/menu/conversation_list_search_results_actions.xml @@ -22,8 +22,7 @@ <item android:id="@+id/search" android:title="@string/menu_search" app:showAsAction="ifRoom|collapseActionView" - android:icon="@drawable/ic_menu_search" - app:actionLayout="@layout/mail_actionbar_searchview" /> + android:icon="@drawable/ic_menu_search" /> <!-- This invisible menu item is used to map CTRL-C to the compose action --> <item diff --git a/res/values-ldrtl/styles-ldrtl.xml b/res/values-ldrtl/styles-ldrtl.xml index 047db0a6e..20b69bf32 100644 --- a/res/values-ldrtl/styles-ldrtl.xml +++ b/res/values-ldrtl/styles-ldrtl.xml @@ -396,6 +396,13 @@ <item name="android:paddingEnd">@dimen/undo_icon_padding_end</item> </style> + <style name="SearchViewLeadingButton"> + <item name="android:layout_gravity">center_vertical</item> + <item name="android:paddingStart">16dp</item> + <item name="android:paddingEnd">32dp</item> + <item name="android:scaleType">center</item> + </style> + <style name="AccountItemNameStyle"> <item name="android:paddingStart">@dimen/account_item_name_start_padding</item> <item name="android:paddingEnd">@dimen/account_item_name_end_padding</item> diff --git a/res/values-sw600dp/themes.xml b/res/values-sw600dp/themes.xml index 4909a9d81..d6babb92f 100644 --- a/res/values-sw600dp/themes.xml +++ b/res/values-sw600dp/themes.xml @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="MailActivityTheme" parent="@style/UnifiedEmailTheme.Appcompat" /> - <!-- todo - comment out when theme exists --> <!--<style name="ShortcutWidgetTheme" parent="@style/Theme.Appcompat.Light.Dialog.MinWidth">--> <!--<item name="actionOverflowButtonStyle">@style/ActionBarOverflowButtonStyle</item>--> diff --git a/res/values/colors.xml b/res/values/colors.xml index 73f685b3f..c3834a84d 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -103,6 +103,9 @@ <color name="notification_icon_gmail_red">#da4336</color> <!-- Search colors --> + <color name="search_query_hint_text">@color/light_gray</color> + <color name="search_query_text">@color/text_color_black</color> + <color name="search_suggestion_item_text">@color/text_color_grey</color> <color name="search_banner_bg">#f6f6f6</color> <color name="search_banner_text">@color/text_color_grey</color> diff --git a/res/values/dimen.xml b/res/values/dimen.xml index 458ae243b..6be9b7114 100644 --- a/res/values/dimen.xml +++ b/res/values/dimen.xml @@ -112,7 +112,6 @@ <dimen name="widget_margin_left">0dip</dimen> <dimen name="widget_margin_right">0dip</dimen> <dimen name="widget_margin_bottom">0dip</dimen> - <dimen name="search_view_width">400dip</dimen> <dimen name="wait_padding">16dp</dimen> <integer name="chips_max_lines">2</integer> <dimen name="tile_letter_font_size">24dp</dimen> @@ -154,6 +153,9 @@ <dimen name="widget_senders_padding_end">16dip</dimen> <dimen name="widget_attachment_padding_end">8dp</dimen> + <dimen name="search_leading_button_width">72dp</dimen> + <dimen name="search_ending_button_width">56dp</dimen> + <dimen name="search_suggestion_height">56dp</dimen> <dimen name="search_results_padding">16dp</dimen> <dimen name="search_banner_text_size">12sp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b66b05f2..f0ce86f04 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -19,10 +19,6 @@ <!-- Names of packages and authorities that are common to all apps and read from resources --> - <!-- Name of the search suggestions authority that looks up recent suggestions. This - needs to be modified in AndroidManifest.xml and res/xml/searchable.xml as well. --> - <string name="suggestions_authority" translatable="false">com.android.mail.suggestionsprovider</string> - <!-- Layout tests strings --> <string name="mock_content_provider" translatable="false">Mock Content Provider</string> <string name="conversation_content_provider" translatable="false">Conversation Content Provider</string> @@ -607,9 +603,11 @@ <!-- Title of the search dialog --> <string name="search_title" translatable="false">Unified Email</string> <!-- Shown in light gray in the Search box when no text has been entered [CHAR LIMIT=20]--> - <string name="search_hint">Search mail</string> + <string name="search_hint">Search</string> <!-- Search Results: Text for status of the search when the results are completely loaded [CHAR LIMIT=10] --> <string name="search_results_loaded"><xliff:g id="searchCount">%1$d</xliff:g></string> + <!-- Voice search is not supported on this device [CHAR LIMIT=100] --> + <string name="voice_search_not_supported">Voice search is not supported on this device.</string> <!-- Shown in conversation list footer when application cannot make a connection [CHAR LIMIT=20]--> <string name="network_error">No connection</string> diff --git a/res/values/styles.xml b/res/values/styles.xml index 544fd24a0..4fca44e70 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -975,11 +975,11 @@ <item name="android:paddingRight">@dimen/undo_icon_padding_end</item> </style> - <style name="Theme.AppCompat.Translucent"> - <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="android:windowIsTranslucent">true</item> - <item name="android:windowAnimationStyle">@android:style/Animation</item> + <style name="SearchViewLeadingButton"> + <item name="android:layout_gravity">center_vertical</item> + <item name="android:paddingLeft">16dp</item> + <item name="android:paddingRight">32dp</item> + <item name="android:scaleType">center</item> </style> <style name="AccountItemNameStyle"> diff --git a/res/values/themes.xml b/res/values/themes.xml index c2edf9f5e..7fd779e47 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -45,4 +45,11 @@ <item name="android:windowNoDisplay">true</item> </style> + <style name="Theme.AppCompat.Translucent"> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:colorBackgroundCacheHint">@null</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowAnimationStyle">@android:style/Animation</item> + </style> + </resources>
\ 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 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> - -<!-- Override the searchSuggestAuthority in the inheriting applications to point to the - correct package name.--> -<searchable xmlns:android="http://schemas.android.com/apk/res/android" - android:label="@string/search_title" - android:hint="@string/search_hint" - android:icon="@drawable/ic_menu_search" - android:searchSuggestIntentAction="android.intent.action.SEARCH" - android:searchSuggestAuthority="com.android.mail" - android:imeOptions="actionSearch" - android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" /> 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. <i>required</i> - * - * @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<String> 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<Cursor> cursors = new ArrayList<Cursor>(); // 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<String> 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<SuggestionItem> 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<String, Void, List<SuggestionItem>> { + @Override + protected List<SuggestionItem> doInBackground(String... strings) { + String query = strings[0]; + if (query == null) { + query = ""; + } + + Cursor c = null; + final List<SuggestionItem> 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<SuggestionItem> 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<Void, Void, Void>() { + @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<Void, Void, Void>() { @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; } } |