• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Google Inc.
3  * Licensed to The Android Open Source Project.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mail.ui;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.app.Activity;
23 import android.content.ActivityNotFoundException;
24 import android.content.Intent;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.speech.RecognizerIntent;
28 import android.text.TextUtils;
29 import android.view.View;
30 import android.widget.Toast;
31 
32 import com.android.mail.ConversationListContext;
33 import com.android.mail.R;
34 import com.android.mail.providers.SearchRecentSuggestionsProvider;
35 import com.android.mail.utils.ViewUtils;
36 
37 import java.util.Locale;
38 
39 /**
40  * Controller for interactions between ActivityController and our custom search views.
41  */
42 public class MaterialSearchViewController implements ViewMode.ModeChangeListener,
43         TwoPaneLayout.ConversationListLayoutListener {
44     private static final long FADE_IN_OUT_DURATION_MS = 150;
45 
46     // The controller is not in search mode. Both search action bar and the suggestion list
47     // are not visible to the user.
48     public static final int SEARCH_VIEW_STATE_GONE = 0;
49     // The controller is actively in search (as in the action bar is focused and the user can type
50     // into the search query). Both the search action bar and the suggestion list are visible.
51     public static final int SEARCH_VIEW_STATE_VISIBLE = 1;
52     // The controller is in a search ViewMode but not actively searching. This is relevant when
53     // we have to show the search actionbar on top while the user is not interacting with it.
54     public static final int SEARCH_VIEW_STATE_ONLY_ACTIONBAR = 2;
55 
56     private static final String EXTRA_CONTROLLER_STATE = "extraSearchViewControllerViewState";
57 
58     private MailActivity mActivity;
59     private ActivityController mController;
60 
61     private SearchRecentSuggestionsProvider mSuggestionsProvider;
62 
63     private MaterialSearchActionView mSearchActionView;
64     private MaterialSearchSuggestionsList mSearchSuggestionList;
65 
66     private int mViewMode;
67     private int mControllerState;
68     private int mEndXCoordForTabletLandscape;
69 
70     private boolean mSavePending;
71     private boolean mDestroyProvider;
72 
MaterialSearchViewController(MailActivity activity, ActivityController controller, Intent intent, Bundle savedInstanceState)73     public MaterialSearchViewController(MailActivity activity, ActivityController controller,
74             Intent intent, Bundle savedInstanceState) {
75         mActivity = activity;
76         mController = controller;
77 
78         final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
79         final boolean supportVoice =
80                 voiceIntent.resolveActivity(mActivity.getPackageManager()) != null;
81 
82         mSuggestionsProvider = mActivity.getSuggestionsProvider();
83         mSearchSuggestionList = (MaterialSearchSuggestionsList) mActivity.findViewById(
84                 R.id.search_overlay_view);
85         mSearchSuggestionList.setController(this, mSuggestionsProvider);
86         mSearchActionView = (MaterialSearchActionView) mActivity.findViewById(
87                 R.id.search_actionbar_view);
88         mSearchActionView.setController(this, intent.getStringExtra(
89                 ConversationListContext.EXTRA_SEARCH_QUERY), supportVoice);
90 
91         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_CONTROLLER_STATE)) {
92             mControllerState = savedInstanceState.getInt(EXTRA_CONTROLLER_STATE);
93         }
94 
95         mActivity.getViewMode().addListener(this);
96     }
97 
98     /**
99      * This controller should not be used after this is called.
100      */
onDestroy()101     public void onDestroy() {
102         mDestroyProvider = mSavePending;
103         if (!mSavePending) {
104             mSuggestionsProvider.cleanup();
105         }
106         mActivity.getViewMode().removeListener(this);
107         mActivity = null;
108         mController = null;
109         mSearchActionView = null;
110         mSearchSuggestionList = null;
111     }
112 
saveState(Bundle outState)113     public void saveState(Bundle outState) {
114         outState.putInt(EXTRA_CONTROLLER_STATE, mControllerState);
115     }
116 
117     @Override
onViewModeChanged(int newMode)118     public void onViewModeChanged(int newMode) {
119         final int oldMode = mViewMode;
120         mViewMode = newMode;
121         // Never animate visibility changes that are caused by view state changes.
122         if (mController.shouldShowSearchBarByDefault(mViewMode)) {
123             showSearchActionBar(SEARCH_VIEW_STATE_ONLY_ACTIONBAR, false /* animate */);
124         } else if (oldMode == ViewMode.UNKNOWN) {
125             showSearchActionBar(mControllerState, false /* animate */);
126         } else {
127             showSearchActionBar(SEARCH_VIEW_STATE_GONE, false /* animate */);
128         }
129     }
130 
131     @Override
onConversationListLayout(int xEnd, boolean drawerOpen)132     public void onConversationListLayout(int xEnd, boolean drawerOpen) {
133         // Only care about the first layout
134         if (mEndXCoordForTabletLandscape != xEnd) {
135             // This is called when we get into tablet landscape mode
136             mEndXCoordForTabletLandscape = xEnd;
137             if (ViewMode.isSearchMode(mViewMode)) {
138                 final int defaultVisibility = mController.shouldShowSearchBarByDefault(mViewMode) ?
139                         View.VISIBLE : View.GONE;
140                 setViewVisibilityAndAlpha(mSearchActionView,
141                         drawerOpen ? View.INVISIBLE : defaultVisibility);
142             }
143             adjustViewForTwoPaneLandscape();
144         }
145     }
146 
handleBackPress()147     public boolean handleBackPress() {
148         final boolean shouldShowSearchBar = mController.shouldShowSearchBarByDefault(mViewMode);
149         if (shouldShowSearchBar && mSearchSuggestionList.isShown()) {
150             showSearchActionBar(SEARCH_VIEW_STATE_ONLY_ACTIONBAR);
151             return true;
152         } else if (!shouldShowSearchBar && mSearchActionView.isShown()) {
153             showSearchActionBar(SEARCH_VIEW_STATE_GONE);
154             return true;
155         }
156         return false;
157     }
158 
159     /**
160      * Set the new visibility state of the search controller.
161      * @param state the new view state, must be one of the following options:
162      *   {@link MaterialSearchViewController#SEARCH_VIEW_STATE_ONLY_ACTIONBAR},
163      *   {@link MaterialSearchViewController#SEARCH_VIEW_STATE_VISIBLE},
164      *   {@link MaterialSearchViewController#SEARCH_VIEW_STATE_GONE},
165      */
showSearchActionBar(int state)166     public void showSearchActionBar(int state) {
167         // By default animate the visibility changes
168         showSearchActionBar(state, true /* animate */);
169     }
170 
171     /**
172      * @param animate if true, the search bar and suggestion list will fade in/out of view.
173      */
showSearchActionBar(int state, boolean animate)174     public void showSearchActionBar(int state, boolean animate) {
175         mControllerState = state;
176 
177         // ACTIONBAR is only applicable in search mode
178         final boolean onlyActionBar = state == SEARCH_VIEW_STATE_ONLY_ACTIONBAR &&
179                 mController.shouldShowSearchBarByDefault(mViewMode);
180         final boolean isStateVisible = state == SEARCH_VIEW_STATE_VISIBLE;
181 
182         final boolean isSearchBarVisible = isStateVisible || onlyActionBar;
183 
184         final int searchBarVisibility = isSearchBarVisible ? View.VISIBLE : View.GONE;
185         final int suggestionListVisibility = isStateVisible ? View.VISIBLE : View.GONE;
186         if (animate) {
187             fadeInOutView(mSearchActionView, searchBarVisibility);
188             fadeInOutView(mSearchSuggestionList, suggestionListVisibility);
189         } else {
190             setViewVisibilityAndAlpha(mSearchActionView, searchBarVisibility);
191             setViewVisibilityAndAlpha(mSearchSuggestionList, suggestionListVisibility);
192         }
193         mSearchActionView.focusSearchBar(isStateVisible);
194 
195         final boolean useDefaultColor = !isSearchBarVisible || shouldAlignWithTl();
196         final int statusBarColor = useDefaultColor ? R.color.mail_activity_status_bar_color :
197                 R.color.search_status_bar_color;
198         ViewUtils.setStatusBarColor(mActivity, statusBarColor);
199 
200         // Specific actions for each view state
201         if (onlyActionBar) {
202             adjustViewForTwoPaneLandscape();
203         } else if (isStateVisible) {
204             // Set to default layout/assets
205             mSearchActionView.adjustViewForTwoPaneLandscape(false /* do not align */, 0);
206         } else {
207             // For non-search view mode, clear the query term for search
208             if (!ViewMode.isSearchMode(mViewMode)) {
209                 mSearchActionView.clearSearchQuery();
210             }
211         }
212     }
213 
214     /**
215      * Helper function to fade in/out the provided view by animating alpha.
216      */
fadeInOutView(final View v, final int visibility)217     private void fadeInOutView(final View v, final int visibility) {
218         if (visibility == View.VISIBLE) {
219             v.setVisibility(View.VISIBLE);
220             v.animate()
221                     .alpha(1f)
222                     .setDuration(FADE_IN_OUT_DURATION_MS)
223                     .setListener(null);
224         } else {
225             v.animate()
226                     .alpha(0f)
227                     .setDuration(FADE_IN_OUT_DURATION_MS)
228                     .setListener(new AnimatorListenerAdapter() {
229                         @Override
230                         public void onAnimationEnd(Animator animation) {
231                             v.setVisibility(visibility);
232                         }
233                     });
234         }
235     }
236 
237     /**
238      * Sets the view's visibility and alpha so that we are guaranteed that alpha = 1 when the view
239      * is visible, and alpha = 0 otherwise.
240      */
setViewVisibilityAndAlpha(View v, int visibility)241     private void setViewVisibilityAndAlpha(View v, int visibility) {
242         v.setVisibility(visibility);
243         if (visibility == View.VISIBLE) {
244             v.setAlpha(1f);
245         } else {
246             v.setAlpha(0f);
247         }
248     }
249 
shouldAlignWithTl()250     private boolean shouldAlignWithTl() {
251         return mController.isTwoPaneLandscape() &&
252                 mControllerState == SEARCH_VIEW_STATE_ONLY_ACTIONBAR &&
253                 ViewMode.isSearchMode(mViewMode);
254     }
255 
adjustViewForTwoPaneLandscape()256     private void adjustViewForTwoPaneLandscape() {
257         // Try to adjust if the layout happened already
258         if (mEndXCoordForTabletLandscape != 0) {
259             mSearchActionView.adjustViewForTwoPaneLandscape(shouldAlignWithTl(),
260                     mEndXCoordForTabletLandscape);
261         }
262     }
263 
onQueryTextChanged(String query)264     public void onQueryTextChanged(String query) {
265         mSearchSuggestionList.setQuery(query);
266     }
267 
onSearchCanceled()268     public void onSearchCanceled() {
269         // Special case search mode
270         if (ViewMode.isSearchMode(mViewMode)) {
271             mActivity.setResult(Activity.RESULT_OK);
272             mActivity.finish();
273         } else {
274             mSearchActionView.clearSearchQuery();
275             showSearchActionBar(SEARCH_VIEW_STATE_GONE);
276         }
277     }
278 
onSearchPerformed(String query)279     public void onSearchPerformed(String query) {
280         query = query.trim();
281         if (!TextUtils.isEmpty(query)) {
282             mSearchActionView.clearSearchQuery();
283             mController.executeSearch(query);
284         }
285     }
286 
onVoiceSearch()287     public void onVoiceSearch() {
288         final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
289         intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
290                 RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
291         intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().getLanguage());
292 
293         // Some devices do not support the voice-to-speech functionality.
294         try {
295             mActivity.startActivityForResult(intent,
296                     AbstractActivityController.VOICE_SEARCH_REQUEST_CODE);
297         } catch (ActivityNotFoundException e) {
298             final String toast =
299                     mActivity.getResources().getString(R.string.voice_search_not_supported);
300             Toast.makeText(mActivity, toast, Toast.LENGTH_LONG).show();
301         }
302     }
303 
saveRecentQuery(String query)304     public void saveRecentQuery(String query) {
305         new SaveRecentQueryTask().execute(query);
306     }
307 
308     // static asynctask to save the query in the background.
309     private class SaveRecentQueryTask extends AsyncTask<String, Void, Void> {
310 
311         @Override
onPreExecute()312         protected void onPreExecute() {
313             mSavePending = true;
314         }
315 
316         @Override
doInBackground(String... args)317         protected Void doInBackground(String... args) {
318             mSuggestionsProvider.saveRecentQuery(args[0]);
319             return null;
320         }
321 
322         @Override
onPostExecute(Void aVoid)323         protected void onPostExecute(Void aVoid) {
324             if (mDestroyProvider) {
325                 mSuggestionsProvider.cleanup();
326                 mDestroyProvider = false;
327             }
328             mSavePending = false;
329         }
330     }
331 }
332