blob: d0719eeca04e4f2f587dde58bab292cb4c72adb1 [file] [log] [blame]
Mihai Nita3b70b0f2015-12-15 16:06:20 -08001/*
Mihai Nita1808ff72016-01-12 08:53:54 -08002 * Copyright (C) 2016 The Android Open Source Project
Mihai Nita3b70b0f2015-12-15 16:06:20 -08003 *
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
Mihai Nita1808ff72016-01-12 08:53:54 -080019import android.app.FragmentManager;
20import android.app.FragmentTransaction;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080021import android.app.ListFragment;
22import android.content.Context;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080023import android.os.Bundle;
Yohei Yukawa23cbe852016-05-17 16:42:58 -070024import android.os.LocaleList;
Mihai Nitac743e0f2016-03-04 10:40:01 -080025import android.text.TextUtils;
Mihai Nita1808ff72016-01-12 08:53:54 -080026import android.view.Menu;
27import android.view.MenuInflater;
28import android.view.MenuItem;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080029import android.view.View;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080030import android.widget.ListView;
Mihai Nita1808ff72016-01-12 08:53:54 -080031import android.widget.SearchView;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080032
Mihai Nita1808ff72016-01-12 08:53:54 -080033import com.android.internal.R;
34
Mihai Nita3b70b0f2015-12-15 16:06:20 -080035import java.util.Collections;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080036import java.util.HashSet;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080037import java.util.Locale;
Mihai Nita1808ff72016-01-12 08:53:54 -080038import java.util.Set;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080039
Mihai Nita1808ff72016-01-12 08:53:54 -080040/**
41 * A two-step locale picker. It shows a language, then a country.
42 *
43 * <p>It shows suggestions at the top, then the rest of the locales.
44 * Allows the user to search for locales using both their native name and their name in the
45 * default locale.</p>
46 */
47public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
Mihai Nita1711d432016-06-15 16:34:50 -070048 private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
Mihai Nita3b70b0f2015-12-15 16:06:20 -080049
Mihai Nita1808ff72016-01-12 08:53:54 -080050 private SuggestedLocaleAdapter mAdapter;
51 private LocaleSelectedListener mListener;
52 private Set<LocaleStore.LocaleInfo> mLocaleList;
53 private LocaleStore.LocaleInfo mParentLocale;
54 private boolean mTranslatedOnly = false;
Mihai Nitac743e0f2016-03-04 10:40:01 -080055 private SearchView mSearchView = null;
56 private CharSequence mPreviousSearch = null;
57 private boolean mPreviousSearchHadFocus = false;
58 private int mFirstVisiblePosition = 0;
59 private int mTopDistance = 0;
Mihai Nita3b70b0f2015-12-15 16:06:20 -080060
Mihai Nita1808ff72016-01-12 08:53:54 -080061 /**
62 * Other classes can register to be notified when a locale was selected.
63 *
64 * <p>This is the mechanism to "return" the result of the selection.</p>
65 */
66 public interface LocaleSelectedListener {
67 /**
68 * The classes that want to retrieve the locale picked should implement this method.
69 * @param locale the locale picked.
70 */
71 void onLocaleSelected(LocaleStore.LocaleInfo locale);
Mihai Nita3b70b0f2015-12-15 16:06:20 -080072 }
73
Mihai Nita1808ff72016-01-12 08:53:54 -080074 private static LocalePickerWithRegion createCountryPicker(Context context,
75 LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
76 boolean translatedOnly) {
77 LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
78 boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
Mihai Nitaf1f39cf2016-02-29 14:42:12 -080079 translatedOnly);
Mihai Nita1808ff72016-01-12 08:53:54 -080080 return shouldShowTheList ? localePicker : null;
81 }
Mihai Nita3b70b0f2015-12-15 16:06:20 -080082
Mihai Nita1808ff72016-01-12 08:53:54 -080083 public static LocalePickerWithRegion createLanguagePicker(Context context,
84 LocaleSelectedListener listener, boolean translatedOnly) {
85 LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
Mihai Nitaf1f39cf2016-02-29 14:42:12 -080086 localePicker.setListener(context, listener, /* parent */ null, translatedOnly);
Mihai Nita1808ff72016-01-12 08:53:54 -080087 return localePicker;
88 }
89
90 /**
91 * Sets the listener and initializes the locale list.
92 *
93 * <p>Returns true if we need to show the list, false if not.</p>
94 *
95 * <p>Can return false because of an error, trying to show a list of countries,
96 * but no parent locale was provided.</p>
97 *
98 * <p>It can also return false if the caller tries to show the list in country mode and
99 * there is only one country available (i.e. Japanese => Japan).
100 * In this case we don't even show the list, we call the listener with that locale,
101 * "pretending" it was selected, and return false.</p>
102 */
103 private boolean setListener(Context context, LocaleSelectedListener listener,
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800104 LocaleStore.LocaleInfo parent, boolean translatedOnly) {
Mihai Nita1808ff72016-01-12 08:53:54 -0800105 this.mParentLocale = parent;
106 this.mListener = listener;
107 this.mTranslatedOnly = translatedOnly;
108 setRetainInstance(true);
109
110 final HashSet<String> langTagsToIgnore = new HashSet<>();
111 if (!translatedOnly) {
112 final LocaleList userLocales = LocalePicker.getLocales();
113 final String[] langTags = userLocales.toLanguageTags().split(",");
114 Collections.addAll(langTagsToIgnore, langTags);
115 }
116
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800117 if (parent != null) {
Mihai Nita1808ff72016-01-12 08:53:54 -0800118 mLocaleList = LocaleStore.getLevelLocales(context,
119 langTagsToIgnore, parent, translatedOnly);
120 if (mLocaleList.size() <= 1) {
121 if (listener != null && (mLocaleList.size() == 1)) {
122 listener.onLocaleSelected(mLocaleList.iterator().next());
123 }
124 return false;
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800125 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800126 } else {
127 mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
128 null /* no parent */, translatedOnly);
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800129 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800130
131 return true;
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800132 }
133
Mihai Nita1711d432016-06-15 16:34:50 -0700134 private void returnToParentFrame() {
135 getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME,
136 FragmentManager.POP_BACK_STACK_INCLUSIVE);
137 }
138
Mihai Nita1808ff72016-01-12 08:53:54 -0800139 @Override
140 public void onCreate(Bundle savedInstanceState) {
141 super.onCreate(savedInstanceState);
142 setHasOptionsMenu(true);
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800143
Mihai Nita1711d432016-06-15 16:34:50 -0700144 if (mLocaleList == null) {
145 // The fragment was killed and restored by the FragmentManager.
146 // At this point we have no data, no listener. Just return, to prevend a NPE.
147 // Fixes b/28748150. Created b/29400003 for a cleaner solution.
148 returnToParentFrame();
149 return;
150 }
151
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800152 final boolean countryMode = mParentLocale != null;
153 final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
154 mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode);
Mihai Nitadf1ccbd2016-02-02 12:51:29 -0800155 final LocaleHelper.LocaleInfoComparator comp =
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800156 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
Mihai Nita1808ff72016-01-12 08:53:54 -0800157 mAdapter.sort(comp);
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800158 setListAdapter(mAdapter);
159 }
160
Mihai Nita1808ff72016-01-12 08:53:54 -0800161 @Override
162 public boolean onOptionsItemSelected(MenuItem menuItem) {
163 int id = menuItem.getItemId();
164 switch (id) {
165 case android.R.id.home:
166 getFragmentManager().popBackStack();
167 return true;
168 }
169 return super.onOptionsItemSelected(menuItem);
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800170 }
171
172 @Override
173 public void onResume() {
174 super.onResume();
Mihai Nitadf1ccbd2016-02-02 12:51:29 -0800175
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800176 if (mParentLocale != null) {
Mihai Nitac743e0f2016-03-04 10:40:01 -0800177 getActivity().setTitle(mParentLocale.getFullNameNative());
Mihai Nitadf1ccbd2016-02-02 12:51:29 -0800178 } else {
Mihai Nitac743e0f2016-03-04 10:40:01 -0800179 getActivity().setTitle(R.string.language_selection_title);
Mihai Nitadf1ccbd2016-02-02 12:51:29 -0800180 }
181
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800182 getListView().requestFocus();
183 }
184
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800185 @Override
Mihai Nitac743e0f2016-03-04 10:40:01 -0800186 public void onPause() {
187 super.onPause();
188
189 // Save search status
190 if (mSearchView != null) {
191 mPreviousSearchHadFocus = mSearchView.hasFocus();
192 mPreviousSearch = mSearchView.getQuery();
193 } else {
194 mPreviousSearchHadFocus = false;
195 mPreviousSearch = null;
196 }
197
198 // Save scroll position
199 final ListView list = getListView();
200 final View firstChild = list.getChildAt(0);
201 mFirstVisiblePosition = list.getFirstVisiblePosition();
202 mTopDistance = (firstChild == null) ? 0 : (firstChild.getTop() - list.getPaddingTop());
203 }
204
205 @Override
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800206 public void onListItemClick(ListView l, View v, int position, long id) {
Mihai Nita1808ff72016-01-12 08:53:54 -0800207 final LocaleStore.LocaleInfo locale =
208 (LocaleStore.LocaleInfo) getListAdapter().getItem(position);
209
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800210 if (locale.getParent() != null) {
Mihai Nita1808ff72016-01-12 08:53:54 -0800211 if (mListener != null) {
212 mListener.onLocaleSelected(locale);
213 }
Mihai Nita1711d432016-06-15 16:34:50 -0700214 returnToParentFrame();
Mihai Nita1808ff72016-01-12 08:53:54 -0800215 } else {
216 LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
217 getContext(), mListener, locale, mTranslatedOnly /* translate only */);
218 if (selector != null) {
219 getFragmentManager().beginTransaction()
220 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
221 .replace(getId(), selector).addToBackStack(null)
222 .commit();
223 } else {
Mihai Nita1711d432016-06-15 16:34:50 -0700224 returnToParentFrame();
Mihai Nita1808ff72016-01-12 08:53:54 -0800225 }
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800226 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800227 }
228
229 @Override
230 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800231 if (mParentLocale == null) {
Mihai Nita1808ff72016-01-12 08:53:54 -0800232 inflater.inflate(R.menu.language_selection_list, menu);
233
Mihai Nitac743e0f2016-03-04 10:40:01 -0800234 final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
235 mSearchView = (SearchView) searchMenuItem.getActionView();
Mihai Nita1808ff72016-01-12 08:53:54 -0800236
237 mSearchView.setQueryHint(getText(R.string.search_language_hint));
238 mSearchView.setOnQueryTextListener(this);
Mihai Nitac743e0f2016-03-04 10:40:01 -0800239
240 // Restore previous search status
Tarandeep Singh9c7600c2017-10-18 17:38:50 +0000241 if (!TextUtils.isEmpty(mPreviousSearch)) {
Mihai Nitac743e0f2016-03-04 10:40:01 -0800242 searchMenuItem.expandActionView();
243 mSearchView.setIconified(false);
244 mSearchView.setActivated(true);
245 if (mPreviousSearchHadFocus) {
246 mSearchView.requestFocus();
247 }
248 mSearchView.setQuery(mPreviousSearch, true /* submit */);
249 } else {
250 mSearchView.setQuery(null, false /* submit */);
251 }
252
253 // Restore previous scroll position
254 getListView().setSelectionFromTop(mFirstVisiblePosition, mTopDistance);
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800255 }
256 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800257
258 @Override
259 public boolean onQueryTextSubmit(String query) {
260 return false;
261 }
262
263 @Override
264 public boolean onQueryTextChange(String newText) {
265 if (mAdapter != null) {
266 mAdapter.getFilter().filter(newText);
267 }
268 return false;
269 }
Mihai Nita3b70b0f2015-12-15 16:06:20 -0800270}