• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.activities;
18 
19 import android.animation.ValueAnimator;
20 import android.app.ActionBar;
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.content.res.TypedArray;
24 import android.os.Bundle;
25 import android.preference.PreferenceManager;
26 import android.text.Editable;
27 import android.text.TextUtils;
28 import android.text.TextWatcher;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.inputmethod.InputMethodManager;
33 import android.widget.SearchView;
34 import android.widget.SearchView.OnCloseListener;
35 import android.view.View.OnClickListener;
36 import android.widget.EditText;
37 import android.widget.Toolbar;
38 
39 import com.android.contacts.R;
40 import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
41 import com.android.contacts.list.ContactsRequest;
42 
43 /**
44  * Adapter for the action bar at the top of the Contacts activity.
45  */
46 public class ActionBarAdapter implements OnCloseListener {
47 
48     public interface Listener {
49         public abstract class Action {
50             public static final int CHANGE_SEARCH_QUERY = 0;
51             public static final int START_SEARCH_MODE = 1;
52             public static final int STOP_SEARCH_MODE = 2;
53         }
54 
onAction(int action)55         void onAction(int action);
56 
57         /**
58          * Called when the user selects a tab.  The new tab can be obtained using
59          * {@link #getCurrentTab}.
60          */
onSelectedTabChanged()61         void onSelectedTabChanged();
62 
onUpButtonPressed()63         void onUpButtonPressed();
64     }
65 
66     private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
67     private static final String EXTRA_KEY_QUERY = "navBar.query";
68     private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
69 
70     private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
71 
72     private boolean mSearchMode;
73     private String mQueryString;
74 
75     private EditText mSearchView;
76     /** The view that represents tabs when we are in portrait mode **/
77     private View mPortraitTabs;
78     /** The view that represents tabs when we are in landscape mode **/
79     private View mLandscapeTabs;
80     private View mSearchContainer;
81 
82     private int mMaxPortraitTabHeight;
83     private int mMaxToolbarContentInsetStart;
84 
85     private final Context mContext;
86     private final SharedPreferences mPrefs;
87 
88     private Listener mListener;
89 
90     private final ActionBar mActionBar;
91     private final Toolbar mToolbar;
92 
93     private boolean mShowHomeIcon;
94 
95     public interface TabState {
96         public static int FAVORITES = 0;
97         public static int ALL = 1;
98 
99         public static int COUNT = 2;
100         public static int DEFAULT = ALL;
101     }
102 
103     private int mCurrentTab = TabState.DEFAULT;
104 
ActionBarAdapter(Context context, Listener listener, ActionBar actionBar, View portraitTabs, View landscapeTabs, Toolbar toolbar)105     public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar,
106             View portraitTabs, View landscapeTabs, Toolbar toolbar) {
107         mContext = context;
108         mListener = listener;
109         mActionBar = actionBar;
110         mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
111         mPortraitTabs = portraitTabs;
112         mLandscapeTabs = landscapeTabs;
113         mToolbar = toolbar;
114         mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart();
115         mShowHomeIcon = mContext.getResources().getBoolean(R.bool.show_home_icon);
116 
117         setupSearchView();
118         setupTabs(context);
119     }
120 
setupTabs(Context context)121     private void setupTabs(Context context) {
122         final TypedArray attributeArray = context.obtainStyledAttributes(
123                 new int[]{android.R.attr.actionBarSize});
124         mMaxPortraitTabHeight = attributeArray.getDimensionPixelSize(0, 0);
125         // Hide tabs initially
126         setPortraitTabHeight(0);
127     }
128 
setupSearchView()129     private void setupSearchView() {
130         final LayoutInflater inflater = (LayoutInflater) mToolbar.getContext().getSystemService(
131                 Context.LAYOUT_INFLATER_SERVICE);
132         mSearchContainer = inflater.inflate(R.layout.search_bar_expanded, mToolbar,
133                 /* attachToRoot = */ false);
134         mSearchContainer.setVisibility(View.VISIBLE);
135         mToolbar.addView(mSearchContainer);
136 
137         mSearchContainer.setBackgroundColor(mContext.getResources().getColor(
138                 R.color.searchbox_background_color));
139         mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view);
140         mSearchView.setHint(mContext.getString(R.string.hint_findContacts));
141         mSearchView.addTextChangedListener(new SearchTextWatcher());
142         mSearchContainer.findViewById(R.id.search_close_button).setOnClickListener(
143                 new OnClickListener() {
144             @Override
145             public void onClick(View v) {
146                 mSearchView.setText(null);
147             }
148         });
149         mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
150                 new OnClickListener() {
151             @Override
152             public void onClick(View v) {
153                 if (mListener != null) {
154                     mListener.onUpButtonPressed();
155                 }
156             }
157         });
158     }
159 
initialize(Bundle savedState, ContactsRequest request)160     public void initialize(Bundle savedState, ContactsRequest request) {
161         if (savedState == null) {
162             mSearchMode = request.isSearchMode();
163             mQueryString = request.getQueryString();
164             mCurrentTab = loadLastTabPreference();
165         } else {
166             mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
167             mQueryString = savedState.getString(EXTRA_KEY_QUERY);
168 
169             // Just set to the field here.  The listener will be notified by update().
170             mCurrentTab = savedState.getInt(EXTRA_KEY_SELECTED_TAB);
171         }
172         if (mCurrentTab >= TabState.COUNT || mCurrentTab < 0) {
173             // Invalid tab index was saved (b/12938207). Restore the default.
174             mCurrentTab = TabState.DEFAULT;
175         }
176         // Show tabs or the expanded {@link SearchView}, depending on whether or not we are in
177         // search mode.
178         update(true /* skipAnimation */);
179         // Expanding the {@link SearchView} clears the query, so set the query from the
180         // {@link ContactsRequest} after it has been expanded, if applicable.
181         if (mSearchMode && !TextUtils.isEmpty(mQueryString)) {
182             setQueryString(mQueryString);
183         }
184     }
185 
setListener(Listener listener)186     public void setListener(Listener listener) {
187         mListener = listener;
188     }
189 
190     private class SearchTextWatcher implements TextWatcher {
191 
192         @Override
onTextChanged(CharSequence queryString, int start, int before, int count)193         public void onTextChanged(CharSequence queryString, int start, int before, int count) {
194             if (queryString.equals(mQueryString)) {
195                 return;
196             }
197             mQueryString = queryString.toString();
198             if (!mSearchMode) {
199                 if (!TextUtils.isEmpty(queryString)) {
200                     setSearchMode(true);
201                 }
202             } else if (mListener != null) {
203                 mListener.onAction(Action.CHANGE_SEARCH_QUERY);
204             }
205         }
206 
207         @Override
afterTextChanged(Editable s)208         public void afterTextChanged(Editable s) {}
209 
210         @Override
beforeTextChanged(CharSequence s, int start, int count, int after)211         public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
212     }
213 
214     /**
215      * Save the current tab selection, and notify the listener.
216      */
setCurrentTab(int tab)217     public void setCurrentTab(int tab) {
218         setCurrentTab(tab, true);
219     }
220 
221     /**
222      * Save the current tab selection.
223      */
setCurrentTab(int tab, boolean notifyListener)224     public void setCurrentTab(int tab, boolean notifyListener) {
225         if (tab == mCurrentTab) {
226             return;
227         }
228         mCurrentTab = tab;
229 
230         if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
231         saveLastTabPreference(mCurrentTab);
232     }
233 
getCurrentTab()234     public int getCurrentTab() {
235         return mCurrentTab;
236     }
237 
238     /**
239      * @return Whether in search mode, i.e. if the search view is visible/expanded.
240      *
241      * Note even if the action bar is in search mode, if the query is empty, the search fragment
242      * will not be in search mode.
243      */
isSearchMode()244     public boolean isSearchMode() {
245         return mSearchMode;
246     }
247 
setSearchMode(boolean flag)248     public void setSearchMode(boolean flag) {
249         if (mSearchMode != flag) {
250             mSearchMode = flag;
251             update(false /* skipAnimation */);
252             if (mSearchView == null) {
253                 return;
254             }
255             if (mSearchMode) {
256                 setFocusOnSearchView();
257             } else {
258                 mSearchView.setText(null);
259             }
260         } else if (flag) {
261             // Everything is already set up. Still make sure the keyboard is up
262             if (mSearchView != null) setFocusOnSearchView();
263         }
264     }
265 
getQueryString()266     public String getQueryString() {
267         return mSearchMode ? mQueryString : null;
268     }
269 
setQueryString(String query)270     public void setQueryString(String query) {
271         mQueryString = query;
272         if (mSearchView != null) {
273             mSearchView.setText(query);
274         }
275     }
276 
277     /** @return true if the "UP" icon is showing. */
isUpShowing()278     public boolean isUpShowing() {
279         return mSearchMode; // Only shown on the search mode.
280     }
281 
updateDisplayOptionsInner()282     private void updateDisplayOptionsInner() {
283         // All the flags we may change in this method.
284         final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
285                 | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM;
286 
287         // The current flags set to the action bar.  (only the ones that we may change here)
288         final int current = mActionBar.getDisplayOptions() & MASK;
289 
290         // Build the new flags...
291         int newFlags = 0;
292         if (mShowHomeIcon && !mSearchMode) {
293             newFlags |= ActionBar.DISPLAY_SHOW_HOME;
294         }
295         if (mSearchMode) {
296             newFlags |= ActionBar.DISPLAY_SHOW_CUSTOM;
297             mToolbar.setContentInsetsRelative(0, mToolbar.getContentInsetEnd());
298         } else {
299             newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
300             mToolbar.setContentInsetsRelative(mMaxToolbarContentInsetStart,
301                     mToolbar.getContentInsetEnd());
302         }
303 
304 
305         if (current != newFlags) {
306             // Pass the mask here to preserve other flags that we're not interested here.
307             mActionBar.setDisplayOptions(newFlags, MASK);
308         }
309     }
310 
update(boolean skipAnimation)311     private void update(boolean skipAnimation) {
312         final boolean isIconifiedChanging
313                 = (mSearchContainer.getParent() == null) == mSearchMode;
314         if (isIconifiedChanging && !skipAnimation) {
315             mToolbar.removeView(mLandscapeTabs);
316             if (mSearchMode) {
317                 addSearchContainer();
318                 mSearchContainer.setAlpha(0);
319                 mSearchContainer.animate().alpha(1);
320                 animateTabHeightChange(mMaxPortraitTabHeight, 0);
321                 updateDisplayOptions(isIconifiedChanging);
322             } else {
323                 mSearchContainer.setAlpha(1);
324                 animateTabHeightChange(0, mMaxPortraitTabHeight);
325                 mSearchContainer.animate().alpha(0).withEndAction(new Runnable() {
326                     @Override
327                     public void run() {
328                         updateDisplayOptionsInner();
329                         updateDisplayOptions(isIconifiedChanging);
330                         addLandscapeViewPagerTabs();
331                         mToolbar.removeView(mSearchContainer);
332                     }
333                 });
334             }
335             return;
336         }
337         if (isIconifiedChanging && skipAnimation) {
338             mToolbar.removeView(mLandscapeTabs);
339             if (mSearchMode) {
340                 setPortraitTabHeight(0);
341                 addSearchContainer();
342             } else {
343                 setPortraitTabHeight(mMaxPortraitTabHeight);
344                 mToolbar.removeView(mSearchContainer);
345                 addLandscapeViewPagerTabs();
346             }
347         }
348         updateDisplayOptions(isIconifiedChanging);
349     }
350 
addLandscapeViewPagerTabs()351     private void addLandscapeViewPagerTabs() {
352         if (mLandscapeTabs != null) {
353             mToolbar.removeView(mLandscapeTabs);
354             mToolbar.addView(mLandscapeTabs);
355         }
356     }
357 
addSearchContainer()358     private void addSearchContainer() {
359         mToolbar.removeView(mSearchContainer);
360         mToolbar.addView(mSearchContainer);
361     }
362 
updateDisplayOptions(boolean isIconifiedChanging)363     private void updateDisplayOptions(boolean isIconifiedChanging) {
364         if (mSearchMode) {
365             setFocusOnSearchView();
366             // Since we have the {@link SearchView} in a custom action bar, we must manually handle
367             // expanding the {@link SearchView} when a search is initiated. Note that a side effect
368             // of this method is that the {@link SearchView} query text is set to empty string.
369             if (isIconifiedChanging) {
370                 final CharSequence queryText = mSearchView.getText();
371                 if (!TextUtils.isEmpty(queryText)) {
372                     mSearchView.setText(queryText);
373                 }
374             }
375             if (mListener != null) {
376                 mListener.onAction(Action.START_SEARCH_MODE);
377             }
378         } else {
379             if (mListener != null) {
380                 mListener.onAction(Action.STOP_SEARCH_MODE);
381                 mListener.onSelectedTabChanged();
382             }
383         }
384         updateDisplayOptionsInner();
385     }
386 
387     @Override
onClose()388     public boolean onClose() {
389         setSearchMode(false);
390         return false;
391     }
392 
onSaveInstanceState(Bundle outState)393     public void onSaveInstanceState(Bundle outState) {
394         outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
395         outState.putString(EXTRA_KEY_QUERY, mQueryString);
396         outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab);
397     }
398 
399     /**
400      * Clears the focus from the {@link SearchView} if we are in search mode.
401      * This will suppress the IME if it is visible.
402      */
clearFocusOnSearchView()403     public void clearFocusOnSearchView() {
404         if (isSearchMode()) {
405             if (mSearchView != null) {
406                 mSearchView.clearFocus();
407             }
408         }
409     }
410 
setFocusOnSearchView()411     public void setFocusOnSearchView() {
412         mSearchView.requestFocus();
413         showInputMethod(mSearchView); // Workaround for the "IME not popping up" issue.
414     }
415 
showInputMethod(View view)416     private void showInputMethod(View view) {
417         final InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
418                 Context.INPUT_METHOD_SERVICE);
419         if (imm != null) {
420             imm.showSoftInput(view, 0);
421         }
422     }
423 
saveLastTabPreference(int tab)424     private void saveLastTabPreference(int tab) {
425         mPrefs.edit().putInt(PERSISTENT_LAST_TAB, tab).apply();
426     }
427 
loadLastTabPreference()428     private int loadLastTabPreference() {
429         try {
430             return mPrefs.getInt(PERSISTENT_LAST_TAB, TabState.DEFAULT);
431         } catch (IllegalArgumentException e) {
432             // Preference is corrupt?
433             return TabState.DEFAULT;
434         }
435     }
436 
animateTabHeightChange(int start, int end)437     private void animateTabHeightChange(int start, int end) {
438         if (mPortraitTabs == null) {
439             return;
440         }
441         final ValueAnimator animator = ValueAnimator.ofInt(start, end);
442         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
443             @Override
444             public void onAnimationUpdate(ValueAnimator valueAnimator) {
445                 int value = (Integer) valueAnimator.getAnimatedValue();
446                 setPortraitTabHeight(value);
447             }
448         });
449         animator.setDuration(100).start();
450     }
451 
setPortraitTabHeight(int height)452     private void setPortraitTabHeight(int height) {
453         if (mPortraitTabs == null) {
454             return;
455         }
456         ViewGroup.LayoutParams layoutParams = mPortraitTabs.getLayoutParams();
457         layoutParams.height = height;
458         mPortraitTabs.setLayoutParams(layoutParams);
459     }
460 }
461