ActionBarAdapter.java revision 250ce43794cdf6820f7a13ef0195a566bd0c8c64
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
17package com.android.contacts.activities;
18
19import com.android.contacts.R;
20import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
21import com.android.contacts.list.ContactsRequest;
22
23import android.app.ActionBar;
24import android.app.ActionBar.LayoutParams;
25import android.app.ActionBar.Tab;
26import android.app.FragmentTransaction;
27import android.content.Context;
28import android.content.res.TypedArray;
29import android.os.Bundle;
30import android.text.TextUtils;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.widget.SearchView;
34import android.widget.SearchView.OnCloseListener;
35import android.widget.SearchView.OnQueryTextListener;
36
37/**
38 * Adapter for the action bar at the top of the Contacts activity.
39 */
40public class ActionBarAdapter implements OnQueryTextListener, OnCloseListener {
41
42    public interface Listener {
43        public enum Action {
44            CHANGE_SEARCH_QUERY, START_SEARCH_MODE, STOP_SEARCH_MODE
45        }
46
47        void onAction(Action action);
48
49        /**
50         * Called when the user selects a tab.  The new tab can be obtained using
51         * {@link #getCurrentTab}.
52         */
53        void onSelectedTabChanged();
54    }
55
56    private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
57    private static final String EXTRA_KEY_QUERY = "navBar.query";
58    private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
59
60    private boolean mSearchMode;
61    private String mQueryString;
62
63    private String mSearchLabelText;
64    private SearchView mSearchView;
65
66    private final Context mContext;
67    private final boolean mAlwaysShowSearchView;
68
69    private Listener mListener;
70
71    private final ActionBar mActionBar;
72    private final MyTabListener mTabListener = new MyTabListener();
73
74    public enum TabState {
75        FAVORITES, ALL, GROUPS;
76
77        public static TabState fromInt(int value) {
78            switch (value) {
79                case 0:
80                    return FAVORITES;
81                case 1:
82                    return ALL;
83                case 2:
84                    return GROUPS;
85            }
86            throw new IllegalArgumentException("Invalid value: " + value);
87        }
88    }
89
90    private TabState mCurrentTab = TabState.FAVORITES;
91
92    public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar) {
93        mContext = context;
94        mListener = listener;
95        mActionBar = actionBar;
96        mSearchLabelText = mContext.getString(R.string.search_label);
97        mAlwaysShowSearchView = mContext.getResources().getBoolean(R.bool.always_show_search_view);
98
99        // Set up search view.
100        View customSearchView = LayoutInflater.from(mContext).inflate(R.layout.custom_action_bar,
101                null);
102        int searchViewWidth = mContext.getResources().getDimensionPixelSize(
103                R.dimen.search_view_width);
104        if (searchViewWidth == 0) {
105            searchViewWidth = LayoutParams.MATCH_PARENT;
106        }
107        LayoutParams layoutParams = new LayoutParams(searchViewWidth, LayoutParams.WRAP_CONTENT);
108        mSearchView = (SearchView) customSearchView.findViewById(R.id.search_view);
109        mSearchView.setQueryHint(mContext.getString(R.string.hint_findContacts));
110        mSearchView.setOnQueryTextListener(this);
111        mSearchView.setOnCloseListener(this);
112        mSearchView.setQuery(mQueryString, false);
113        mActionBar.setCustomView(customSearchView, layoutParams);
114
115        mActionBar.setDisplayShowTitleEnabled(true);
116
117        // TODO Just use a boolean resource instead of styles.
118        TypedArray array = mContext.obtainStyledAttributes(null, R.styleable.ActionBarHomeIcon);
119        boolean showHomeIcon = array.getBoolean(R.styleable.ActionBarHomeIcon_show_home_icon, true);
120        array.recycle();
121        mActionBar.setDisplayShowHomeEnabled(showHomeIcon);
122
123        addTab(TabState.FAVORITES, mContext.getString(R.string.contactsFavoritesLabel));
124        addTab(TabState.ALL, mContext.getString(R.string.contactsAllLabel));
125        addTab(TabState.GROUPS, mContext.getString(R.string.contactsGroupsLabel));
126    }
127
128    public void initialize(Bundle savedState, ContactsRequest request) {
129        if (savedState == null) {
130            mSearchMode = request.isSearchMode();
131            mQueryString = request.getQueryString();
132        } else {
133            mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
134            mQueryString = savedState.getString(EXTRA_KEY_QUERY);
135
136            // Just set to the field here.  The listener will be notified by update().
137            mCurrentTab = TabState.fromInt(savedState.getInt(EXTRA_KEY_SELECTED_TAB));
138        }
139        update();
140    }
141
142    public void setListener(Listener listener) {
143        mListener = listener;
144    }
145
146    private void addTab(TabState tabState, String text) {
147        final Tab tab = mActionBar.newTab();
148        tab.setTag(tabState);
149        tab.setText(text);
150        tab.setTabListener(mTabListener);
151        mActionBar.addTab(tab);
152    }
153
154    private class MyTabListener implements ActionBar.TabListener {
155        /**
156         * If true, it won't call {@link #setCurrentTab} in {@link #onTabSelected}.
157         * This flag is used when we want to programmatically update the current tab without
158         * {@link #onTabSelected} getting called.
159         */
160        public boolean mIgnoreTabSelected;
161
162        @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { }
163        @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { }
164
165        @Override public void onTabSelected(Tab tab, FragmentTransaction ft) {
166            if (!mIgnoreTabSelected) {
167                setCurrentTab((TabState)tab.getTag());
168            }
169        }
170    }
171
172    /**
173     * Change the current tab, and notify the listener.
174     */
175    public void setCurrentTab(TabState tab) {
176        setCurrentTab(tab, true);
177    }
178
179    /**
180     * Change the current tab
181     */
182    public void setCurrentTab(TabState tab, boolean notifyListener) {
183        if (tab == null) throw new NullPointerException();
184        if (tab == mCurrentTab) {
185            return;
186        }
187        mCurrentTab = tab;
188
189        int index = mCurrentTab.ordinal();
190        if ((mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
191                && (index != mActionBar.getSelectedNavigationIndex())) {
192            mActionBar.setSelectedNavigationItem(index);
193        }
194
195        if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
196    }
197
198    public TabState getCurrentTab() {
199        return mCurrentTab;
200    }
201
202    public boolean isSearchMode() {
203        return mSearchMode;
204    }
205
206    public void setSearchMode(boolean flag) {
207        if (mSearchMode != flag) {
208            mSearchMode = flag;
209            update();
210            if (mSearchView == null) {
211                return;
212            }
213            if (mSearchMode) {
214                setFocusOnSearchView();
215            } else {
216                mSearchView.setQuery(null, false);
217            }
218        }
219    }
220
221    public String getQueryString() {
222        return mQueryString;
223    }
224
225    public void setQueryString(String query) {
226        mQueryString = query;
227        if (mSearchView != null) {
228            mSearchView.setQuery(query, false);
229        }
230    }
231
232    private void update() {
233        if (mSearchMode) {
234            mActionBar.setDisplayShowCustomEnabled(true);
235            if (mAlwaysShowSearchView) {
236                // Tablet -- change the app title for the search mode
237                mActionBar.setTitle(mSearchLabelText);
238            } else {
239                // Phone -- search view gets focus
240                setFocusOnSearchView();
241            }
242            if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) {
243                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
244            }
245            if (mListener != null) {
246                mListener.onAction(Action.START_SEARCH_MODE);
247            }
248        } else {
249            mActionBar.setDisplayShowCustomEnabled(mAlwaysShowSearchView);
250            if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_TABS) {
251                // setNavigationMode will trigger onTabSelected() with the tab which was previously
252                // selected.
253                // The issue is that when we're first switching to the tab navigation mode after
254                // screen orientation changes, onTabSelected() will get called with the first tab
255                // (i.e. favorite), which would results in mCurrentTab getting set to FAVORITES and
256                // we'd lose restored tab.
257                // So let's just disable the callback here temporarily.  We'll notify the listener
258                // after this anyway.
259                mTabListener.mIgnoreTabSelected = true;
260                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
261                mActionBar.setSelectedNavigationItem(mCurrentTab.ordinal());
262                mTabListener.mIgnoreTabSelected = false;
263            }
264            mActionBar.setTitle(null);
265            if (mListener != null) {
266                mListener.onAction(Action.STOP_SEARCH_MODE);
267                mListener.onSelectedTabChanged();
268            }
269        }
270    }
271
272    @Override
273    public boolean onQueryTextChange(String queryString) {
274        // TODO: Clean up SearchView code because it keeps setting the SearchView query,
275        // invoking onQueryChanged, setting up the fragment again, invalidating the options menu,
276        // storing the SearchView again, and etc... unless we add in the early return statements.
277        if (queryString.equals(mQueryString)) {
278            return false;
279        }
280        mQueryString = queryString;
281        if (!mSearchMode) {
282            if (!TextUtils.isEmpty(queryString)) {
283                setSearchMode(true);
284            }
285        } else if (mListener != null) {
286            mListener.onAction(Action.CHANGE_SEARCH_QUERY);
287        }
288
289        return true;
290    }
291
292    @Override
293    public boolean onQueryTextSubmit(String query) {
294        return true;
295    }
296
297    @Override
298    public boolean onClose() {
299        setSearchMode(false);
300        return false;
301    }
302
303    public void onSaveInstanceState(Bundle outState) {
304        outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
305        outState.putString(EXTRA_KEY_QUERY, mQueryString);
306        outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab.ordinal());
307    }
308
309    private void setFocusOnSearchView() {
310        mSearchView.requestFocus();
311        mSearchView.setIconified(false); // Workaround for the "IME not popping up" issue.
312    }
313}
314