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 17 package com.android.internal.app; 18 19 import android.app.FragmentManager; 20 import android.app.FragmentTransaction; 21 import android.app.ListFragment; 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.os.LocaleList; 25 import android.text.TextUtils; 26 import android.view.Menu; 27 import android.view.MenuInflater; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.widget.ListView; 31 import android.widget.SearchView; 32 33 import com.android.internal.R; 34 35 import java.util.Collections; 36 import java.util.HashSet; 37 import java.util.Locale; 38 import java.util.Set; 39 40 /** 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 */ 47 public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener { 48 private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; 49 50 private SuggestedLocaleAdapter mAdapter; 51 private LocaleSelectedListener mListener; 52 private Set<LocaleStore.LocaleInfo> mLocaleList; 53 private LocaleStore.LocaleInfo mParentLocale; 54 private boolean mTranslatedOnly = false; 55 private SearchView mSearchView = null; 56 private CharSequence mPreviousSearch = null; 57 private boolean mPreviousSearchHadFocus = false; 58 private int mFirstVisiblePosition = 0; 59 private int mTopDistance = 0; 60 61 /** 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 */ onLocaleSelected(LocaleStore.LocaleInfo locale)71 void onLocaleSelected(LocaleStore.LocaleInfo locale); 72 } 73 createCountryPicker(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly)74 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, 79 translatedOnly); 80 return shouldShowTheList ? localePicker : null; 81 } 82 createLanguagePicker(Context context, LocaleSelectedListener listener, boolean translatedOnly)83 public static LocalePickerWithRegion createLanguagePicker(Context context, 84 LocaleSelectedListener listener, boolean translatedOnly) { 85 LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); 86 localePicker.setListener(context, listener, /* parent */ null, translatedOnly); 87 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 */ setListener(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly)103 private boolean setListener(Context context, LocaleSelectedListener listener, 104 LocaleStore.LocaleInfo parent, boolean translatedOnly) { 105 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 117 if (parent != null) { 118 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; 125 } 126 } else { 127 mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, 128 null /* no parent */, translatedOnly); 129 } 130 131 return true; 132 } 133 returnToParentFrame()134 private void returnToParentFrame() { 135 getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME, 136 FragmentManager.POP_BACK_STACK_INCLUSIVE); 137 } 138 139 @Override onCreate(Bundle savedInstanceState)140 public void onCreate(Bundle savedInstanceState) { 141 super.onCreate(savedInstanceState); 142 setHasOptionsMenu(true); 143 144 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 152 final boolean countryMode = mParentLocale != null; 153 final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault(); 154 mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode); 155 final LocaleHelper.LocaleInfoComparator comp = 156 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode); 157 mAdapter.sort(comp); 158 setListAdapter(mAdapter); 159 } 160 161 @Override onViewCreated(View view, Bundle savedInstanceState)162 public void onViewCreated(View view, Bundle savedInstanceState) { 163 super.onViewCreated(view, savedInstanceState); 164 // In order to make the list view work with CollapsingToolbarLayout, 165 // we have to enable the nested scrolling feature of the list view. 166 getListView().setNestedScrollingEnabled(true); 167 } 168 169 @Override onOptionsItemSelected(MenuItem menuItem)170 public boolean onOptionsItemSelected(MenuItem menuItem) { 171 int id = menuItem.getItemId(); 172 switch (id) { 173 case android.R.id.home: 174 getFragmentManager().popBackStack(); 175 return true; 176 } 177 return super.onOptionsItemSelected(menuItem); 178 } 179 180 @Override onResume()181 public void onResume() { 182 super.onResume(); 183 184 if (mParentLocale != null) { 185 getActivity().setTitle(mParentLocale.getFullNameNative()); 186 } else { 187 getActivity().setTitle(R.string.language_selection_title); 188 } 189 190 getListView().requestFocus(); 191 } 192 193 @Override onPause()194 public void onPause() { 195 super.onPause(); 196 197 // Save search status 198 if (mSearchView != null) { 199 mPreviousSearchHadFocus = mSearchView.hasFocus(); 200 mPreviousSearch = mSearchView.getQuery(); 201 } else { 202 mPreviousSearchHadFocus = false; 203 mPreviousSearch = null; 204 } 205 206 // Save scroll position 207 final ListView list = getListView(); 208 final View firstChild = list.getChildAt(0); 209 mFirstVisiblePosition = list.getFirstVisiblePosition(); 210 mTopDistance = (firstChild == null) ? 0 : (firstChild.getTop() - list.getPaddingTop()); 211 } 212 213 @Override onListItemClick(ListView l, View v, int position, long id)214 public void onListItemClick(ListView l, View v, int position, long id) { 215 final LocaleStore.LocaleInfo locale = 216 (LocaleStore.LocaleInfo) getListAdapter().getItem(position); 217 218 if (locale.getParent() != null) { 219 if (mListener != null) { 220 mListener.onLocaleSelected(locale); 221 } 222 returnToParentFrame(); 223 } else { 224 LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( 225 getContext(), mListener, locale, mTranslatedOnly /* translate only */); 226 if (selector != null) { 227 getFragmentManager().beginTransaction() 228 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 229 .replace(getId(), selector).addToBackStack(null) 230 .commit(); 231 } else { 232 returnToParentFrame(); 233 } 234 } 235 } 236 237 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)238 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 239 if (mParentLocale == null) { 240 inflater.inflate(R.menu.language_selection_list, menu); 241 242 final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu); 243 mSearchView = (SearchView) searchMenuItem.getActionView(); 244 245 mSearchView.setQueryHint(getText(R.string.search_language_hint)); 246 mSearchView.setOnQueryTextListener(this); 247 248 // Restore previous search status 249 if (!TextUtils.isEmpty(mPreviousSearch)) { 250 searchMenuItem.expandActionView(); 251 mSearchView.setIconified(false); 252 mSearchView.setActivated(true); 253 if (mPreviousSearchHadFocus) { 254 mSearchView.requestFocus(); 255 } 256 mSearchView.setQuery(mPreviousSearch, true /* submit */); 257 } else { 258 mSearchView.setQuery(null, false /* submit */); 259 } 260 261 // Restore previous scroll position 262 getListView().setSelectionFromTop(mFirstVisiblePosition, mTopDistance); 263 } 264 } 265 266 @Override onQueryTextSubmit(String query)267 public boolean onQueryTextSubmit(String query) { 268 return false; 269 } 270 271 @Override onQueryTextChange(String newText)272 public boolean onQueryTextChange(String newText) { 273 if (mAdapter != null) { 274 mAdapter.getFilter().filter(newText); 275 } 276 return false; 277 } 278 } 279