ActionBarAdapter.java revision 311882ab2fc1e9c8264f5892a6d50722b834e348
108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov/*
2fcfdffb439ccf1ddef971109b82e782c47161572Dmitri Plotnikov * Copyright (C) 2010 The Android Open Source Project
308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov *
408e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * Licensed under the Apache License, Version 2.0 (the "License");
508e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * you may not use this file except in compliance with the License.
608e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * You may obtain a copy of the License at
708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov *
808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov *      http://www.apache.org/licenses/LICENSE-2.0
908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov *
1008e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * Unless required by applicable law or agreed to in writing, software
1108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * distributed under the License is distributed on an "AS IS" BASIS,
1208e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * See the License for the specific language governing permissions and
1408e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov * limitations under the License.
1508e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov */
1608e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
1708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikovpackage com.android.contacts.activities;
1808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
1908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikovimport com.android.contacts.R;
20b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuanimport com.android.contacts.activities.ActionBarAdapter.Listener.Action;
2108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikovimport com.android.contacts.list.ContactsRequest;
2208e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
231a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikovimport android.app.ActionBar;
241ee9df6facd2340416d6a33aeb739707643d2fc3Katherine Kuanimport android.app.ActionBar.LayoutParams;
254d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onukiimport android.app.ActionBar.Tab;
264d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onukiimport android.app.FragmentTransaction;
2708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikovimport android.content.Context;
2835d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onukiimport android.content.SharedPreferences;
2908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikovimport android.os.Bundle;
3035d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onukiimport android.preference.PreferenceManager;
3115ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikovimport android.text.TextUtils;
321ee9df6facd2340416d6a33aeb739707643d2fc3Katherine Kuanimport android.view.LayoutInflater;
331ee9df6facd2340416d6a33aeb739707643d2fc3Katherine Kuanimport android.view.View;
34d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuanimport android.view.inputmethod.InputMethodManager;
351a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikovimport android.widget.SearchView;
361a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikovimport android.widget.SearchView.OnCloseListener;
37c5792ae6760291d435e91e0fdd28ece6b3311439Adam Powellimport android.widget.SearchView.OnQueryTextListener;
3892a6fdf1b07b8f1447560174e24455fd46aee7cdDmitri Plotnikov
3908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov/**
40fcfdffb439ccf1ddef971109b82e782c47161572Dmitri Plotnikov * Adapter for the action bar at the top of the Contacts activity.
4108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov */
42495db43f0a08bd71ba28a42f89b80d38af15afecKatherine Kuanpublic class ActionBarAdapter implements OnQueryTextListener, OnCloseListener {
4308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
4408e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    public interface Listener {
45311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann        public abstract class Action {
46311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann            public static final int CHANGE_SEARCH_QUERY = 0;
47311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann            public static final int START_SEARCH_MODE = 1;
48311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann            public static final int STOP_SEARCH_MODE = 2;
49b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        }
50b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan
51311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann        void onAction(int action);
524d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
534d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        /**
544d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         * Called when the user selects a tab.  The new tab can be obtained using
554d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         * {@link #getCurrentTab}.
564d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         */
574d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        void onSelectedTabChanged();
5808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
5908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
6015ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
6108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    private static final String EXTRA_KEY_QUERY = "navBar.query";
6259dc2751d64f60f743b8a9e78186aa5b79dcaf83Isaac Katzenelson    private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
6308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
6435d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki    private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
6535d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki
6615ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    private boolean mSearchMode;
6708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    private String mQueryString;
6815ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov
6915ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    private SearchView mSearchView;
7092a6fdf1b07b8f1447560174e24455fd46aee7cdDmitri Plotnikov
7108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    private final Context mContext;
7235d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki    private final SharedPreferences mPrefs;
7308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
7408e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    private Listener mListener;
7508e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
764d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    private final ActionBar mActionBar;
774d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    private final MyTabListener mTabListener = new MyTabListener();
788b8264620f9362e089322d1e3a7cc5620900e6f9Dmitri Plotnikov
79e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    private boolean mShowHomeIcon;
80aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan    private boolean mShowTabsAsText;
81e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
8252aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    public interface TabState {
8352aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        public static int GROUPS = 0;
8452aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        public static int ALL = 1;
8552aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        public static int FAVORITES = 2;
8652aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann
8752aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        public static int COUNT = 3;
8852aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        public static int DEFAULT = ALL;
894d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
904d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
9152aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    private int mCurrentTab = TabState.DEFAULT;
924d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
93aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan    public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar,
94aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan            boolean isUsingTwoPanes) {
9508e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov        mContext = context;
961ee9df6facd2340416d6a33aeb739707643d2fc3Katherine Kuan        mListener = listener;
974d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        mActionBar = actionBar;
9835d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
9908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
100e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        mShowHomeIcon = mContext.getResources().getBoolean(R.bool.show_home_icon);
101e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
102aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan        // On wide screens, show the tabs as text (instead of icons)
103aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan        mShowTabsAsText = isUsingTwoPanes;
104aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan
1054ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        // Set up search view.
1060bf7cd0124fa6f74fe3d64619740420ded86dcceKatherine Kuan        View customSearchView = LayoutInflater.from(mActionBar.getThemedContext()).inflate(
1070bf7cd0124fa6f74fe3d64619740420ded86dcceKatherine Kuan                R.layout.custom_action_bar, null);
1084ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        int searchViewWidth = mContext.getResources().getDimensionPixelSize(
1094ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki                R.dimen.search_view_width);
1104ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        if (searchViewWidth == 0) {
1114ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki            searchViewWidth = LayoutParams.MATCH_PARENT;
1121173ae29217fc83f254404f8a5fa10419ee83c93Dmitri Plotnikov        }
1134ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        LayoutParams layoutParams = new LayoutParams(searchViewWidth, LayoutParams.WRAP_CONTENT);
1144ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        mSearchView = (SearchView) customSearchView.findViewById(R.id.search_view);
115d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // Since the {@link SearchView} in this app is "click-to-expand", set the below mode on the
116d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // {@link SearchView} so that the magnifying glass icon appears inside the editable text
117d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // field. (In the "click-to-expand" search pattern, the user must explicitly expand the
118d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // search field and already knows a search is being conducted, so the icon is redundant
119d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // and can go away once the user starts typing.)
120d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        mSearchView.setIconifiedByDefault(true);
1214ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        mSearchView.setQueryHint(mContext.getString(R.string.hint_findContacts));
122c5792ae6760291d435e91e0fdd28ece6b3311439Adam Powell        mSearchView.setOnQueryTextListener(this);
12315ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        mSearchView.setOnCloseListener(this);
12415ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        mSearchView.setQuery(mQueryString, false);
1254ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        mActionBar.setCustomView(customSearchView, layoutParams);
1264ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki
127e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        // Set up tabs
1281ff189a390ce5011f729637baf248646560c1bc6Daisuke Miyakawa        addTab(TabState.GROUPS, R.drawable.ic_tab_groups, R.string.contactsGroupsLabel);
1291ff189a390ce5011f729637baf248646560c1bc6Daisuke Miyakawa        addTab(TabState.ALL, R.drawable.ic_tab_all, R.string.contactsAllLabel);
1301ff189a390ce5011f729637baf248646560c1bc6Daisuke Miyakawa        addTab(TabState.FAVORITES, R.drawable.ic_tab_starred, R.string.contactsFavoritesLabel);
1314d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
1324d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1334d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    public void initialize(Bundle savedState, ContactsRequest request) {
1344d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        if (savedState == null) {
1354d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            mSearchMode = request.isSearchMode();
1364d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            mQueryString = request.getQueryString();
13735d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki            mCurrentTab = loadLastTabPreference();
1384d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        } else {
1394d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
1404d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            mQueryString = savedState.getString(EXTRA_KEY_QUERY);
1414d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1424d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            // Just set to the field here.  The listener will be notified by update().
14352aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann            mCurrentTab = savedState.getInt(EXTRA_KEY_SELECTED_TAB);
1444d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        }
1453c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        // Show tabs or the expanded {@link SearchView}, depending on whether or not we are in
1463c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        // search mode.
1474ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        update();
1483c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        // Expanding the {@link SearchView} clears the query, so set the query from the
1493c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        // {@link ContactsRequest} after it has been expanded, if applicable.
1503c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        if (mSearchMode && !TextUtils.isEmpty(mQueryString)) {
1513c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan            setQueryString(mQueryString);
1523c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan        }
1538b8264620f9362e089322d1e3a7cc5620900e6f9Dmitri Plotnikov    }
1548b8264620f9362e089322d1e3a7cc5620900e6f9Dmitri Plotnikov
1551a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov    public void setListener(Listener listener) {
1561a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov        mListener = listener;
15708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
15808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
15952aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    private void addTab(int expectedTabIndex, int icon, int description) {
1604d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        final Tab tab = mActionBar.newTab();
1614d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        tab.setTabListener(mTabListener);
162aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan        if (mShowTabsAsText) {
163aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan            tab.setText(description);
164aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan        } else {
165aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan            tab.setIcon(icon);
166aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan            tab.setContentDescription(description);
167aaadf9393e98b04a40d249cd97ec758047268ed9Katherine Kuan        }
1684d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        mActionBar.addTab(tab);
16952aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        if (expectedTabIndex != tab.getPosition()) {
17052aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann            throw new IllegalStateException("Tabs must be created in the right order");
17152aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        }
1724d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
1734d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1744d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    private class MyTabListener implements ActionBar.TabListener {
1754d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        /**
1764d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         * If true, it won't call {@link #setCurrentTab} in {@link #onTabSelected}.
1774d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         * This flag is used when we want to programmatically update the current tab without
1784d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         * {@link #onTabSelected} getting called.
1794d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki         */
1804d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        public boolean mIgnoreTabSelected;
1814d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1824d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { }
1834d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { }
1844d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1854d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        @Override public void onTabSelected(Tab tab, FragmentTransaction ft) {
1864d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            if (!mIgnoreTabSelected) {
18752aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann                setCurrentTab(tab.getPosition());
1884d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            }
1894d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        }
1904d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
1914d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
1924d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    /**
1934d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki     * Change the current tab, and notify the listener.
1944d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki     */
19552aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    public void setCurrentTab(int tab) {
196250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki        setCurrentTab(tab, true);
197250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki    }
198250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki
199250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki    /**
200250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki     * Change the current tab
201250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki     */
20252aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    public void setCurrentTab(int tab, boolean notifyListener) {
2034d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        if (tab == mCurrentTab) {
2044d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            return;
2054d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        }
2064d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        mCurrentTab = tab;
2074d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
2084d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        if ((mActionBar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
20952aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann                && (mCurrentTab != mActionBar.getSelectedNavigationIndex())) {
21052aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann            mActionBar.setSelectedNavigationItem(mCurrentTab);
2114d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        }
2124d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
213250ce43794cdf6820f7a13ef0195a566bd0c8c64Makoto Onuki        if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
21435d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki        saveLastTabPreference(mCurrentTab);
2154d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
2164d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
21752aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    public int getCurrentTab() {
2184d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki        return mCurrentTab;
2194d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    }
2204d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki
2211db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki    /**
2221db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki     * @return Whether in search mode, i.e. if the search view is visible/expanded.
2231db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki     *
2241db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki     * Note even if the action bar is in search mode, if the query is empty, the search fragment
2251db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki     * will not be in search mode.
2261db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki     */
22715ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    public boolean isSearchMode() {
22815ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        return mSearchMode;
22908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
23008e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
23115ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    public void setSearchMode(boolean flag) {
23215ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        if (mSearchMode != flag) {
23315ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov            mSearchMode = flag;
234bee8d3bd37edf44c535c0cc401e8d03f095b8328Dmitri Plotnikov            update();
235f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan            if (mSearchView == null) {
236f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan                return;
237f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan            }
2380b51a8d4539cc516dc2c02948335347d1df8769bDmitri Plotnikov            if (mSearchMode) {
2394ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki                setFocusOnSearchView();
240ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov            } else {
241ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov                mSearchView.setQuery(null, false);
2420b51a8d4539cc516dc2c02948335347d1df8769bDmitri Plotnikov            }
243311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann        } else if (flag) {
244311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann            // Everything is already set up. Still make sure the keyboard is up
245311882ab2fc1e9c8264f5892a6d50722b834e348Daniel Lehmann            if (mSearchView != null) setFocusOnSearchView();
24608e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov        }
24708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
24808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
24908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    public String getQueryString() {
2501db00f68b34f6cf7e9d19fedb559cf12f8c05e9cMakoto Onuki        return mSearchMode ? mQueryString : null;
25108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
25208e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
25308e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    public void setQueryString(String query) {
25408e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov        mQueryString = query;
255f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan        if (mSearchView != null) {
256f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan            mSearchView.setQuery(query, false);
257f3f933a798c9cfffb1c1c0cc770187b5b0b763ebKatherine Kuan        }
25808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
25908e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
260e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    /** @return true if the "UP" icon is showing. */
261e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    public boolean isUpShowing() {
262e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        return mSearchMode; // Only shown on the search mode.
263e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    }
264e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
265e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    private void updateDisplayOptions() {
266e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        // All the flags we may change in this method.
267e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
268d84ea5c8b0a8376e7cf80b0e15b65a72fd99d7a9Adam Powell                | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM;
269e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
270e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        // The current flags set to the action bar.  (only the ones that we may change here)
271e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        final int current = mActionBar.getDisplayOptions() & MASK;
272e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
273e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        // Build the new flags...
274e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        int newFlags = 0;
275e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
276e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        if (mShowHomeIcon) {
277e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            newFlags |= ActionBar.DISPLAY_SHOW_HOME;
278e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        }
279e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        if (mSearchMode) {
280e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            newFlags |= ActionBar.DISPLAY_SHOW_HOME;
281e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            newFlags |= ActionBar.DISPLAY_HOME_AS_UP;
282e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            newFlags |= ActionBar.DISPLAY_SHOW_CUSTOM;
283e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        }
284d84ea5c8b0a8376e7cf80b0e15b65a72fd99d7a9Adam Powell        mActionBar.setHomeButtonEnabled(mSearchMode);
285e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
286e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        if (current != newFlags) {
287e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            // Pass the mask here to preserve other flags that we're not interested here.
288e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki            mActionBar.setDisplayOptions(newFlags, MASK);
289e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        }
290e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki    }
291e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki
2924d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki    private void update() {
293b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        if (mSearchMode) {
2945267367e202dcb39ef0fe912a7be9209548ec436Makoto Onuki            setFocusOnSearchView();
295d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            // Since we have the {@link SearchView} in a custom action bar, we must manually handle
2963c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan            // expanding the {@link SearchView} when a search is initiated. Note that a side effect
2973c877e33cb7fecc7a63af1cf3c25061d53811bf6Katherine Kuan            // of this method is that the {@link SearchView} query text is set to empty string.
298d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            mSearchView.onActionViewExpanded();
2994d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) {
3004d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
3014d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            }
302b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan            if (mListener != null) {
303b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan                mListener.onAction(Action.START_SEARCH_MODE);
304c9916d303cc48b4a4ff94ef9c56d7ca5da72c4deDmitri Plotnikov            }
30515ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        } else {
3064d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_TABS) {
3074d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // setNavigationMode will trigger onTabSelected() with the tab which was previously
3084d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // selected.
3094d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // The issue is that when we're first switching to the tab navigation mode after
3104d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // screen orientation changes, onTabSelected() will get called with the first tab
3114d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // (i.e. favorite), which would results in mCurrentTab getting set to FAVORITES and
3124d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // we'd lose restored tab.
3134d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // So let's just disable the callback here temporarily.  We'll notify the listener
3144d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                // after this anyway.
3154d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                mTabListener.mIgnoreTabSelected = true;
3164d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
31752aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann                mActionBar.setSelectedNavigationItem(mCurrentTab);
3184d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                mTabListener.mIgnoreTabSelected = false;
3194d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki            }
320b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan            mActionBar.setTitle(null);
321d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            // Since we have the {@link SearchView} in a custom action bar, we must manually handle
322d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            // collapsing the {@link SearchView} when search mode is exited.
323d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            mSearchView.onActionViewCollapsed();
324b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan            if (mListener != null) {
325b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan                mListener.onAction(Action.STOP_SEARCH_MODE);
3264d788fc27ac855bd6e0cec17dff39a18564f0089Makoto Onuki                mListener.onSelectedTabChanged();
327bee8d3bd37edf44c535c0cc401e8d03f095b8328Dmitri Plotnikov            }
32808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov        }
329e0c66aff27473d0edbd95660b95f0f2a80ab63ecMakoto Onuki        updateDisplayOptions();
33008e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
33108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
33208e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    @Override
333c5792ae6760291d435e91e0fdd28ece6b3311439Adam Powell    public boolean onQueryTextChange(String queryString) {
334b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        // TODO: Clean up SearchView code because it keeps setting the SearchView query,
335b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        // invoking onQueryChanged, setting up the fragment again, invalidating the options menu,
336b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        // storing the SearchView again, and etc... unless we add in the early return statements.
337b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        if (queryString.equals(mQueryString)) {
338b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan            return false;
339b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan        }
34008e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov        mQueryString = queryString;
341ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov        if (!mSearchMode) {
342ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov            if (!TextUtils.isEmpty(queryString)) {
343ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov                setSearchMode(true);
3449806ed099226a19e92c29d9efa791cd187fd2a53Dmitri Plotnikov            }
345ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov        } else if (mListener != null) {
346b5760b94bbf56ce348876ec36f4669d20a1530f5Katherine Kuan            mListener.onAction(Action.CHANGE_SEARCH_QUERY);
3479806ed099226a19e92c29d9efa791cd187fd2a53Dmitri Plotnikov        }
348ab4d29f6a2edbfae008dbaff35f25baad73aa0dfDmitri Plotnikov
3491a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov        return true;
3501a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov    }
3511a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov
3521a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov    @Override
353c5792ae6760291d435e91e0fdd28ece6b3311439Adam Powell    public boolean onQueryTextSubmit(String query) {
354d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // When the search is "committed" by the user, then hide the keyboard so the user can
355d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        // more easily browse the list of results.
356d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        if (mSearchView != null) {
357d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
358d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan                    Context.INPUT_METHOD_SERVICE);
359d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            if (imm != null) {
360d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan                imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
361d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            }
362d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan            mSearchView.clearFocus();
363d8a61b7296bf7388505f0211d7ef44700e766193Katherine Kuan        }
3641a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov        return true;
36508e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
36608e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
36708e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    @Override
3681a59cffd839069ce91a9febb8a136442340ab4c8Dmitri Plotnikov    public boolean onClose() {
36915ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        setSearchMode(false);
37015ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        return false;
37108e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov    }
37208e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov
37315ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov    public void onSaveInstanceState(Bundle outState) {
37415ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
37515ccbb4d22fd65165cacb7970cbe61de1aa9aac4Dmitri Plotnikov        outState.putString(EXTRA_KEY_QUERY, mQueryString);
37652aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab);
377fa49a66979567894c85fe2489d1375216d67597eDmitri Plotnikov    }
378fa49a66979567894c85fe2489d1375216d67597eDmitri Plotnikov
3795c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan    /**
3805c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan     * Clears the focus from the {@link SearchView} if we are in search mode.
3815c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan     * This will suppress the IME if it is visible.
3825c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan     */
3835c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan    public void clearFocusOnSearchView() {
3845c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan        if (isSearchMode()) {
3855c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan            if (mSearchView != null) {
3865c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan                mSearchView.clearFocus();
3875c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan            }
3885c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan        }
3895c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan    }
3905c3a0a1440625fab859aab420cb08bc0276358a0Katherine Kuan
39109d66a382d1f94618b52fec11ba54d9243a95a18Daniel Lehmann    public void setFocusOnSearchView() {
3924ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        mSearchView.requestFocus();
3934ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki        mSearchView.setIconified(false); // Workaround for the "IME not popping up" issue.
3944ba903cdf132a6be2e5efa33187b18ca8446e503Makoto Onuki    }
39535d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki
39652aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    private void saveLastTabPreference(int tab) {
39752aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann        mPrefs.edit().putInt(PERSISTENT_LAST_TAB, tab).apply();
39835d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki    }
39935d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki
40052aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann    private int loadLastTabPreference() {
40135d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki        try {
40252aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann            return mPrefs.getInt(PERSISTENT_LAST_TAB, TabState.DEFAULT);
40335d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki        } catch (IllegalArgumentException e) {
40435d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki            // Preference is corrupt?
40552aa32db432085ab2cd4864bd1edd1ef843da867Daniel Lehmann            return TabState.DEFAULT;
40635d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki        }
40735d4aec605ce915ff5c574892a2069a37e0b9a77Makoto Onuki    }
40808e1c4e49947daee26cfc435d01605febb7ae6faDmitri Plotnikov}
409