blob: e2d29e313be4a7a6a37836c32c7980885aa4a7f3 [file] [log] [blame]
Mihai Nita1808ff72016-01-12 08:53:54 -08001/*
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
Mihai Nita24215ec2016-01-29 12:32:56 -080019import android.text.TextUtils;
Mihai Nita1808ff72016-01-12 08:53:54 -080020import android.view.LayoutInflater;
21import android.view.View;
22import android.view.ViewGroup;
23import android.widget.BaseAdapter;
24import android.widget.Filter;
25import android.widget.Filterable;
Mihai Nita1808ff72016-01-12 08:53:54 -080026import android.widget.TextView;
27
28import com.android.internal.R;
29
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Locale;
33import java.util.Set;
34
35
36/**
37 * This adapter wraps around a regular ListAdapter for LocaleInfo, and creates 2 sections.
38 *
39 * <p>The first section contains "suggested" languages (usually including a region),
40 * the second section contains all the languages within the original adapter.
41 * The "others" might still include languages that appear in the "suggested" section.</p>
42 *
43 * <p>Example: if we show "German Switzerland" as "suggested" (based on SIM, let's say),
44 * then "German" will still show in the "others" section, clicking on it will only show the
45 * countries for all the other German locales, but not Switzerland
46 * (Austria, Belgium, Germany, Liechtenstein, Luxembourg)</p>
47 */
Mihai Nita137b96e2016-01-25 11:31:15 -080048public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
Mihai Nita1808ff72016-01-12 08:53:54 -080049 private static final int TYPE_HEADER_SUGGESTED = 0;
50 private static final int TYPE_HEADER_ALL_OTHERS = 1;
51 private static final int TYPE_LOCALE = 2;
Mihai Nita86235d42016-04-01 11:50:59 -070052 private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6;
Mihai Nita1808ff72016-01-12 08:53:54 -080053
54 private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions;
55 private ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
56 private int mSuggestionCount;
57 private final boolean mCountryMode;
58 private LayoutInflater mInflater;
59
Mihai Nita137b96e2016-01-25 11:31:15 -080060 public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
Mihai Nita1808ff72016-01-12 08:53:54 -080061 mCountryMode = countryMode;
62 mLocaleOptions = new ArrayList<>(localeOptions.size());
63 for (LocaleStore.LocaleInfo li : localeOptions) {
64 if (li.isSuggested()) {
65 mSuggestionCount++;
66 }
67 mLocaleOptions.add(li);
68 }
69 }
70
71 @Override
72 public boolean areAllItemsEnabled() {
73 return false;
74 }
75
76 @Override
77 public boolean isEnabled(int position) {
78 return getItemViewType(position) == TYPE_LOCALE;
79 }
80
81 @Override
82 public int getItemViewType(int position) {
83 if (!showHeaders()) {
84 return TYPE_LOCALE;
85 } else {
86 if (position == 0) {
87 return TYPE_HEADER_SUGGESTED;
88 }
89 if (position == mSuggestionCount + 1) {
90 return TYPE_HEADER_ALL_OTHERS;
91 }
92 return TYPE_LOCALE;
93 }
94 }
95
96 @Override
97 public int getViewTypeCount() {
98 if (showHeaders()) {
99 return 3; // Two headers in addition to the locales
100 } else {
101 return 1; // Locales items only
102 }
103 }
104
105 @Override
106 public int getCount() {
107 if (showHeaders()) {
108 return mLocaleOptions.size() + 2; // 2 extra for the headers
109 } else {
110 return mLocaleOptions.size();
111 }
112 }
113
114 @Override
115 public Object getItem(int position) {
116 int offset = 0;
117 if (showHeaders()) {
118 offset = position > mSuggestionCount ? -2 : -1;
119 }
120
121 return mLocaleOptions.get(position + offset);
122 }
123
124 @Override
125 public long getItemId(int position) {
126 return position;
127 }
128
129 @Override
130 public View getView(int position, View convertView, ViewGroup parent) {
131 if (convertView == null && mInflater == null) {
132 mInflater = LayoutInflater.from(parent.getContext());
133 }
134
135 int itemType = getItemViewType(position);
136 switch (itemType) {
137 case TYPE_HEADER_SUGGESTED: // intentional fallthrough
138 case TYPE_HEADER_ALL_OTHERS:
139 // Covers both null, and "reusing" a wrong kind of view
140 if (!(convertView instanceof TextView)) {
141 convertView = mInflater.inflate(R.layout.language_picker_section_header,
142 parent, false);
143 }
144 TextView textView = (TextView) convertView;
145 if (itemType == TYPE_HEADER_SUGGESTED) {
146 textView.setText(R.string.language_picker_section_suggested);
147 } else {
148 textView.setText(R.string.language_picker_section_all);
149 }
150 textView.setTextLocale(Locale.getDefault());
151 break;
152 default:
153 // Covers both null, and "reusing" a wrong kind of view
154 if (!(convertView instanceof ViewGroup)) {
155 convertView = mInflater.inflate(R.layout.language_picker_item, parent, false);
156 }
157
158 TextView text = (TextView) convertView.findViewById(R.id.locale);
Mihai Nita1808ff72016-01-12 08:53:54 -0800159 LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800160 text.setText(item.getLabel(mCountryMode));
Mihai Nita502141d2016-02-22 16:40:26 -0800161 text.setTextLocale(item.getLocale());
Mihai Nita24215ec2016-01-29 12:32:56 -0800162 if (mCountryMode) {
163 int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
164 //noinspection ResourceType
165 convertView.setLayoutDirection(layoutDir);
166 text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
167 ? View.TEXT_DIRECTION_RTL
168 : View.TEXT_DIRECTION_LTR);
169 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800170 }
171 return convertView;
172 }
173
Mihai Nita1808ff72016-01-12 08:53:54 -0800174 private boolean showHeaders() {
Mihai Nita86235d42016-04-01 11:50:59 -0700175 // We don't want to show suggestions for locales with very few regions
176 // (e.g. Romanian, with 2 regions)
177 // So we put a (somewhat) arbitrary limit.
178 //
179 // The initial idea was to make that limit dependent on the screen height.
180 // But that would mean rotating the screen could make the suggestions disappear,
181 // as the number of countries that fits on the screen would be different in portrait
182 // and landscape mode.
183 if (mCountryMode && mLocaleOptions.size() < MIN_REGIONS_FOR_SUGGESTIONS) {
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800184 return false;
185 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800186 return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size();
187 }
188
Mihai Nita137b96e2016-01-25 11:31:15 -0800189 /**
190 * Sorts the items in the adapter using a locale-aware comparator.
191 * @param comp The locale-aware comparator to use.
192 */
Mihai Nita1808ff72016-01-12 08:53:54 -0800193 public void sort(LocaleHelper.LocaleInfoComparator comp) {
194 Collections.sort(mLocaleOptions, comp);
195 }
196
197 class FilterByNativeAndUiNames extends Filter {
198
199 @Override
200 protected FilterResults performFiltering(CharSequence prefix) {
201 FilterResults results = new FilterResults();
202
203 if (mOriginalLocaleOptions == null) {
204 mOriginalLocaleOptions = new ArrayList<>(mLocaleOptions);
205 }
206
207 ArrayList<LocaleStore.LocaleInfo> values;
208 values = new ArrayList<>(mOriginalLocaleOptions);
209 if (prefix == null || prefix.length() == 0) {
210 results.values = values;
211 results.count = values.size();
212 } else {
213 // TODO: decide if we should use the string's locale
214 Locale locale = Locale.getDefault();
215 String prefixString = LocaleHelper.normalizeForSearch(prefix.toString(), locale);
216
217 final int count = values.size();
218 final ArrayList<LocaleStore.LocaleInfo> newValues = new ArrayList<>();
219
220 for (int i = 0; i < count; i++) {
221 final LocaleStore.LocaleInfo value = values.get(i);
222 final String nameToCheck = LocaleHelper.normalizeForSearch(
223 value.getFullNameInUiLanguage(), locale);
224 final String nativeNameToCheck = LocaleHelper.normalizeForSearch(
225 value.getFullNameNative(), locale);
226 if (wordMatches(nativeNameToCheck, prefixString)
227 || wordMatches(nameToCheck, prefixString)) {
228 newValues.add(value);
229 }
230 }
231
232 results.values = newValues;
233 results.count = newValues.size();
234 }
235
236 return results;
237 }
238
239 // TODO: decide if this is enough, or we want to use a BreakIterator...
240 boolean wordMatches(String valueText, String prefixString) {
Mihai Nita137b96e2016-01-25 11:31:15 -0800241 // First match against the whole, non-split value
Mihai Nita1808ff72016-01-12 08:53:54 -0800242 if (valueText.startsWith(prefixString)) {
243 return true;
244 }
245
246 final String[] words = valueText.split(" ");
247 // Start at index 0, in case valueText starts with space(s)
248 for (String word : words) {
249 if (word.startsWith(prefixString)) {
250 return true;
251 }
252 }
253
254 return false;
255 }
256
257 @Override
Mihai Nita1808ff72016-01-12 08:53:54 -0800258 protected void publishResults(CharSequence constraint, FilterResults results) {
259 mLocaleOptions = (ArrayList<LocaleStore.LocaleInfo>) results.values;
260
261 mSuggestionCount = 0;
262 for (LocaleStore.LocaleInfo li : mLocaleOptions) {
263 if (li.isSuggested()) {
264 mSuggestionCount++;
265 }
266 }
267
268 if (results.count > 0) {
269 notifyDataSetChanged();
270 } else {
271 notifyDataSetInvalidated();
272 }
273 }
274 }
275
276 @Override
277 public Filter getFilter() {
278 return new FilterByNativeAndUiNames();
279 }
280}