• 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 com.android.contacts.R;
20 import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
21 import com.android.contacts.list.ContactsRequest;
22 
23 import android.app.ActionBar;
24 import android.app.ActionBar.LayoutParams;
25 import android.app.ActionBar.Tab;
26 import android.app.FragmentTransaction;
27 import android.content.Context;
28 import android.content.SharedPreferences;
29 import android.os.Bundle;
30 import android.preference.PreferenceManager;
31 import android.text.TextUtils;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.inputmethod.InputMethodManager;
35 import android.widget.SearchView;
36 import android.widget.SearchView.OnCloseListener;
37 import android.widget.SearchView.OnQueryTextListener;
38 
39 /**
40  * Adapter for the action bar at the top of the Contacts activity.
41  */
42 public class ActionBarAdapter implements OnQueryTextListener, OnCloseListener {
43 
44     public interface Listener {
45         public enum Action {
46             CHANGE_SEARCH_QUERY, START_SEARCH_MODE, STOP_SEARCH_MODE
47         }
48 
onAction(Action action)49         void onAction(Action action);
50 
51         /**
52          * Called when the user selects a tab.  The new tab can be obtained using
53          * {@link #getCurrentTab}.
54          */
onSelectedTabChanged()55         void onSelectedTabChanged();
56     }
57 
58     private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
59     private static final String EXTRA_KEY_QUERY = "navBar.query";
60     private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
61 
62     private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
63 
64     private boolean mSearchMode;
65     private String mQueryString;
66 
67     private SearchView mSearchView;
68 
69     private final Context mContext;
70     private final SharedPreferences mPrefs;
71 
72     private Listener mListener;
73 
74     private final ActionBar mActionBar;
75     private final MyTabListener mTabListener = new MyTabListener();
76 
77     private boolean mShowHomeIcon;
78 
79     public enum TabState {
80         GROUPS,
81         ALL,
82         FAVORITES;
83 
fromInt(int value)84         public static TabState fromInt(int value) {
85             if (GROUPS.ordinal() == value) {
86                 return GROUPS;
87             }
88             if (ALL.ordinal() == value) {
89                 return ALL;
90             }
91             if (FAVORITES.ordinal() == value) {
92                 return FAVORITES;
93             }
94             throw new IllegalArgumentException("Invalid value: " + value);
95         }
96     }
97 
98     private static final TabState DEFAULT_TAB = TabState.ALL;
99     private TabState mCurrentTab = DEFAULT_TAB;
100 
ActionBarAdapter(Context context, Listener listener, ActionBar actionBar)101     public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar) {
102         mContext = context;
103         mListener = listener;
104         mActionBar = actionBar;
105         mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
106 
107         mShowHomeIcon = mContext.getResources().getBoolean(R.bool.show_home_icon);
108 
109         // Set up search view.
110         View customSearchView = LayoutInflater.from(mActionBar.getThemedContext()).inflate(
111                 R.layout.custom_action_bar, null);
112         int searchViewWidth = mContext.getResources().getDimensionPixelSize(
113                 R.dimen.search_view_width);
114         if (searchViewWidth == 0) {
115             searchViewWidth = LayoutParams.MATCH_PARENT;
116         }
117         LayoutParams layoutParams = new LayoutParams(searchViewWidth, LayoutParams.WRAP_CONTENT);
118         mSearchView = (SearchView) customSearchView.findViewById(R.id.search_view);
119         // Since the {@link SearchView} in this app is "click-to-expand", set the below mode on the
120         // {@link SearchView} so that the magnifying glass icon appears inside the editable text
121         // field. (In the "click-to-expand" search pattern, the user must explicitly expand the
122         // search field and already knows a search is being conducted, so the icon is redundant
123         // and can go away once the user starts typing.)
124         mSearchView.setIconifiedByDefault(true);
125         mSearchView.setQueryHint(mContext.getString(R.string.hint_findContacts));
126         mSearchView.setOnQueryTextListener(this);
127         mSearchView.setOnCloseListener(this);
128         mSearchView.setQuery(mQueryString, false);
129         mActionBar.setCustomView(customSearchView, layoutParams);
130 
131         // Set up tabs
132         addTab(TabState.GROUPS, R.drawable.ic_tab_groups, R.string.contactsGroupsLabel);
133         addTab(TabState.ALL, R.drawable.ic_tab_all, R.string.contactsAllLabel);
134         addTab(TabState.FAVORITES, R.drawable.ic_tab_starred, R.string.contactsFavoritesLabel);
135     }
136 
initialize(Bundle savedState, ContactsRequest request)137     public void initialize(Bundle savedState, ContactsRequest request) {
138         if (savedState == null) {
139             mSearchMode = request.isSearchMode();
140             mQueryString = request.getQueryString();
141             mCurrentTab = loadLastTabPreference();
142         } else {
143             mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
144             mQueryString = savedState.getString(EXTRA_KEY_QUERY);
145 
146             // Just set to the field here.  The listener will be notified by update().
147             mCurrentTab = TabState.fromInt(savedState.getInt(EXTRA_KEY_SELECTED_TAB));
148         }
149         update();
150     }
151 
setListener(Listener listener)152     public void setListener(Listener listener) {
153         mListener = listener;
154     }
155 
addTab(TabState tabState, int icon, int contentDescription)156     private void addTab(TabState tabState, int icon, int contentDescription) {
157         final Tab tab = mActionBar.newTab();
158         tab.setTag(tabState);
159         tab.setIcon(icon);
160         tab.setContentDescription(contentDescription);
161         tab.setTabListener(mTabListener);
162         mActionBar.addTab(tab);
163     }
164 
165     private class MyTabListener implements ActionBar.TabListener {
166         /**
167          * If true, it won't call {@link #setCurrentTab} in {@link #onTabSelected}.
168          * This flag is used when we want to programmatically update the current tab without
169          * {@link #onTabSelected} getting called.
170          */
171         public boolean mIgnoreTabSelected;
172 
onTabReselected(Tab tab, FragmentTransaction ft)173         @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { }
onTabUnselected(Tab tab, FragmentTransaction ft)174         @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { }
175 
onTabSelected(Tab tab, FragmentTransaction ft)176         @Override public void onTabSelected(Tab tab, FragmentTransaction ft) {
177             if (!mIgnoreTabSelected) {
178                 setCurrentTab((TabState)tab.getTag());
179             }
180         }
181     }
182 
183     /**
184      * Change the current tab, and notify the listener.
185      */
setCurrentTab(TabState tab)186     public void setCurrentTab(TabState tab) {
187         setCurrentTab(tab, true);
188     }
189 
190     /**
191      * Change the current tab
192      */
setCurrentTab(TabState tab, boolean notifyListener)193     public void setCurrentTab(TabState tab, boolean notifyListener) {
194         if (tab == null) throw new NullPointerException();
195         if (tab == mCurrentTab) {
196             return;
197         }
198         mCurrentTab = tab;
199 
200         int index = mCurrentTab.ordinal();
201         if ((mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
202                 && (index != mActionBar.getSelectedNavigationIndex())) {
203             mActionBar.setSelectedNavigationItem(index);
204         }
205 
206         if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
207         saveLastTabPreference(mCurrentTab);
208     }
209 
getCurrentTab()210     public TabState getCurrentTab() {
211         return mCurrentTab;
212     }
213 
isSearchMode()214     public boolean isSearchMode() {
215         return mSearchMode;
216     }
217 
shouldShowSearchResult()218     public boolean shouldShowSearchResult() {
219         return mSearchMode && !TextUtils.isEmpty(mQueryString);
220     }
221 
setSearchMode(boolean flag)222     public void setSearchMode(boolean flag) {
223         if (mSearchMode != flag) {
224             mSearchMode = flag;
225             update();
226             if (mSearchView == null) {
227                 return;
228             }
229             if (mSearchMode) {
230                 setFocusOnSearchView();
231             } else {
232                 mSearchView.setQuery(null, false);
233             }
234         }
235     }
236 
getQueryString()237     public String getQueryString() {
238         return mQueryString;
239     }
240 
setQueryString(String query)241     public void setQueryString(String query) {
242         mQueryString = query;
243         if (mSearchView != null) {
244             mSearchView.setQuery(query, false);
245         }
246     }
247 
248     /** @return true if the "UP" icon is showing. */
isUpShowing()249     public boolean isUpShowing() {
250         return mSearchMode; // Only shown on the search mode.
251     }
252 
updateDisplayOptions()253     private void updateDisplayOptions() {
254         // All the flags we may change in this method.
255         final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
256                 | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM;
257 
258         // The current flags set to the action bar.  (only the ones that we may change here)
259         final int current = mActionBar.getDisplayOptions() & MASK;
260 
261         // Build the new flags...
262         int newFlags = 0;
263         newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
264         if (mShowHomeIcon) {
265             newFlags |= ActionBar.DISPLAY_SHOW_HOME;
266         }
267         if (mSearchMode) {
268             newFlags |= ActionBar.DISPLAY_SHOW_HOME;
269             newFlags |= ActionBar.DISPLAY_HOME_AS_UP;
270             newFlags |= ActionBar.DISPLAY_SHOW_CUSTOM;
271         }
272         mActionBar.setHomeButtonEnabled(mSearchMode);
273 
274         if (current != newFlags) {
275             // Pass the mask here to preserve other flags that we're not interested here.
276             mActionBar.setDisplayOptions(newFlags, MASK);
277         }
278     }
279 
update()280     private void update() {
281         if (mSearchMode) {
282             setFocusOnSearchView();
283             // Since we have the {@link SearchView} in a custom action bar, we must manually handle
284             // expanding the {@link SearchView} when a search is initiated.
285             mSearchView.onActionViewExpanded();
286             if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) {
287                 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
288             }
289             if (mListener != null) {
290                 mListener.onAction(Action.START_SEARCH_MODE);
291             }
292         } else {
293             if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_TABS) {
294                 // setNavigationMode will trigger onTabSelected() with the tab which was previously
295                 // selected.
296                 // The issue is that when we're first switching to the tab navigation mode after
297                 // screen orientation changes, onTabSelected() will get called with the first tab
298                 // (i.e. favorite), which would results in mCurrentTab getting set to FAVORITES and
299                 // we'd lose restored tab.
300                 // So let's just disable the callback here temporarily.  We'll notify the listener
301                 // after this anyway.
302                 mTabListener.mIgnoreTabSelected = true;
303                 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
304                 mActionBar.setSelectedNavigationItem(mCurrentTab.ordinal());
305                 mTabListener.mIgnoreTabSelected = false;
306             }
307             mActionBar.setTitle(null);
308             // Since we have the {@link SearchView} in a custom action bar, we must manually handle
309             // collapsing the {@link SearchView} when search mode is exited.
310             mSearchView.onActionViewCollapsed();
311             if (mListener != null) {
312                 mListener.onAction(Action.STOP_SEARCH_MODE);
313                 mListener.onSelectedTabChanged();
314             }
315         }
316         updateDisplayOptions();
317     }
318 
319     @Override
onQueryTextChange(String queryString)320     public boolean onQueryTextChange(String queryString) {
321         // TODO: Clean up SearchView code because it keeps setting the SearchView query,
322         // invoking onQueryChanged, setting up the fragment again, invalidating the options menu,
323         // storing the SearchView again, and etc... unless we add in the early return statements.
324         if (queryString.equals(mQueryString)) {
325             return false;
326         }
327         mQueryString = queryString;
328         if (!mSearchMode) {
329             if (!TextUtils.isEmpty(queryString)) {
330                 setSearchMode(true);
331             }
332         } else if (mListener != null) {
333             mListener.onAction(Action.CHANGE_SEARCH_QUERY);
334         }
335 
336         return true;
337     }
338 
339     @Override
onQueryTextSubmit(String query)340     public boolean onQueryTextSubmit(String query) {
341         // When the search is "committed" by the user, then hide the keyboard so the user can
342         // more easily browse the list of results.
343         if (mSearchView != null) {
344             InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
345                     Context.INPUT_METHOD_SERVICE);
346             if (imm != null) {
347                 imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
348             }
349             mSearchView.clearFocus();
350         }
351         return true;
352     }
353 
354     @Override
onClose()355     public boolean onClose() {
356         setSearchMode(false);
357         return false;
358     }
359 
onSaveInstanceState(Bundle outState)360     public void onSaveInstanceState(Bundle outState) {
361         outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
362         outState.putString(EXTRA_KEY_QUERY, mQueryString);
363         outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab.ordinal());
364     }
365 
setFocusOnSearchView()366     private void setFocusOnSearchView() {
367         mSearchView.requestFocus();
368         mSearchView.setIconified(false); // Workaround for the "IME not popping up" issue.
369     }
370 
saveLastTabPreference(TabState tab)371     private void saveLastTabPreference(TabState tab) {
372         mPrefs.edit().putInt(PERSISTENT_LAST_TAB, tab.ordinal()).apply();
373     }
374 
loadLastTabPreference()375     private TabState loadLastTabPreference() {
376         try {
377             return TabState.fromInt(mPrefs.getInt(PERSISTENT_LAST_TAB, DEFAULT_TAB.ordinal()));
378         } catch (IllegalArgumentException e) {
379             // Preference is corrupt?
380             return DEFAULT_TAB;
381         }
382     }
383 }
384