LocalePickerWithRegion.java revision df1ccbdc97a6540613c686298b82ea8385577c56
1/*
2 * Copyright (C) 2016 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.internal.app;
18
19import android.app.FragmentManager;
20import android.app.FragmentTransaction;
21import android.app.ListFragment;
22import android.content.Context;
23import android.os.Bundle;
24import android.util.LocaleList;
25import android.view.Menu;
26import android.view.MenuInflater;
27import android.view.MenuItem;
28import android.view.View;
29import android.widget.ListView;
30import android.widget.SearchView;
31
32import com.android.internal.R;
33
34import java.util.Collections;
35import java.util.HashSet;
36import java.util.Locale;
37import java.util.Set;
38
39/**
40 * A two-step locale picker. It shows a language, then a country.
41 *
42 * <p>It shows suggestions at the top, then the rest of the locales.
43 * Allows the user to search for locales using both their native name and their name in the
44 * default locale.</p>
45 */
46public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
47
48    private SuggestedLocaleAdapter mAdapter;
49    private LocaleSelectedListener mListener;
50    private Set<LocaleStore.LocaleInfo> mLocaleList;
51    private LocaleStore.LocaleInfo mParentLocale;
52    private boolean mTranslatedOnly = false;
53    private boolean mCountryMode = false;
54
55    /**
56     * Other classes can register to be notified when a locale was selected.
57     *
58     * <p>This is the mechanism to "return" the result of the selection.</p>
59     */
60    public interface LocaleSelectedListener {
61        /**
62         * The classes that want to retrieve the locale picked should implement this method.
63         * @param locale    the locale picked.
64         */
65        void onLocaleSelected(LocaleStore.LocaleInfo locale);
66    }
67
68    private static LocalePickerWithRegion createCountryPicker(Context context,
69            LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
70            boolean translatedOnly) {
71        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
72        boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
73                true /* country mode */, translatedOnly);
74        return shouldShowTheList ? localePicker : null;
75    }
76
77    public static LocalePickerWithRegion createLanguagePicker(Context context,
78            LocaleSelectedListener listener, boolean translatedOnly) {
79        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
80        localePicker.setListener(context, listener, null,
81                false /* language mode */, translatedOnly);
82        return localePicker;
83    }
84
85    /**
86     * Sets the listener and initializes the locale list.
87     *
88     * <p>Returns true if we need to show the list, false if not.</p>
89     *
90     * <p>Can return false because of an error, trying to show a list of countries,
91     * but no parent locale was provided.</p>
92     *
93     * <p>It can also return false if the caller tries to show the list in country mode and
94     * there is only one country available (i.e. Japanese => Japan).
95     * In this case we don't even show the list, we call the listener with that locale,
96     * "pretending" it was selected, and return false.</p>
97     */
98    private boolean setListener(Context context, LocaleSelectedListener listener,
99            LocaleStore.LocaleInfo parent, boolean countryMode, boolean translatedOnly) {
100        if (countryMode && (parent == null || parent.getLocale() == null)) {
101            // The list of countries is determined as all the countries where the parent language
102            // is used.
103            throw new IllegalArgumentException("The country selection list needs a parent.");
104        }
105
106        this.mCountryMode = countryMode;
107        this.mParentLocale = parent;
108        this.mListener = listener;
109        this.mTranslatedOnly = translatedOnly;
110        setRetainInstance(true);
111
112        final HashSet<String> langTagsToIgnore = new HashSet<>();
113        if (!translatedOnly) {
114            final LocaleList userLocales = LocalePicker.getLocales();
115            final String[] langTags = userLocales.toLanguageTags().split(",");
116            Collections.addAll(langTagsToIgnore, langTags);
117        }
118
119        if (countryMode) {
120            mLocaleList = LocaleStore.getLevelLocales(context,
121                    langTagsToIgnore, parent, translatedOnly);
122            if (mLocaleList.size() <= 1) {
123                if (listener != null && (mLocaleList.size() == 1)) {
124                    listener.onLocaleSelected(mLocaleList.iterator().next());
125                }
126                return false;
127            }
128        } else {
129            mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
130                    null /* no parent */, translatedOnly);
131        }
132
133        return true;
134    }
135
136    @Override
137    public void onCreate(Bundle savedInstanceState) {
138        super.onCreate(savedInstanceState);
139        setHasOptionsMenu(true);
140
141        final Locale sortingLocale = (mCountryMode && mParentLocale != null)
142                ? mParentLocale.getLocale()
143                : Locale.getDefault();
144
145        mAdapter = new SuggestedLocaleAdapter(mLocaleList, mCountryMode);
146        final LocaleHelper.LocaleInfoComparator comp =
147                new LocaleHelper.LocaleInfoComparator(sortingLocale);
148        mAdapter.sort(comp);
149        setListAdapter(mAdapter);
150    }
151
152    @Override
153    public boolean onOptionsItemSelected(MenuItem menuItem) {
154        int id = menuItem.getItemId();
155        switch (id) {
156            case android.R.id.home:
157                getFragmentManager().popBackStack();
158                return true;
159        }
160        return super.onOptionsItemSelected(menuItem);
161    }
162
163    @Override
164    public void onResume() {
165        super.onResume();
166
167        if (mCountryMode) {
168            if (mParentLocale == null) {
169                this.getActivity().setTitle(R.string.country_selection_title);
170            } else {
171                this.getActivity().setTitle(mParentLocale.getFullNameNative());
172            }
173        } else {
174            this.getActivity().setTitle(R.string.language_selection_title);
175        }
176
177        getListView().requestFocus();
178    }
179
180    @Override
181    public void onListItemClick(ListView l, View v, int position, long id) {
182        final LocaleStore.LocaleInfo locale =
183                (LocaleStore.LocaleInfo) getListAdapter().getItem(position);
184
185        if (mCountryMode || locale.getParent() != null) {
186            if (mListener != null) {
187                mListener.onLocaleSelected(locale);
188            }
189            getFragmentManager().popBackStack("localeListEditor",
190                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
191        } else {
192            LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
193                    getContext(), mListener, locale, mTranslatedOnly /* translate only */);
194            if (selector != null) {
195                getFragmentManager().beginTransaction()
196                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
197                        .replace(getId(), selector).addToBackStack(null)
198                        .commit();
199            } else {
200                getFragmentManager().popBackStack("localeListEditor",
201                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
202            }
203        }
204    }
205
206    @Override
207    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
208        if (!mCountryMode) {
209            inflater.inflate(R.menu.language_selection_list, menu);
210
211            MenuItem mSearchMenuItem = menu.findItem(R.id.locale_search_menu);
212            SearchView mSearchView = (SearchView) mSearchMenuItem.getActionView();
213
214            mSearchView.setQueryHint(getText(R.string.search_language_hint));
215            mSearchView.setOnQueryTextListener(this);
216            mSearchView.setQuery("", false /* submit */);
217        }
218    }
219
220    @Override
221    public boolean onQueryTextSubmit(String query) {
222        return false;
223    }
224
225    @Override
226    public boolean onQueryTextChange(String newText) {
227        if (mAdapter != null) {
228            mAdapter.getFilter().filter(newText);
229        }
230        return false;
231    }
232}
233