blob: 98102ea8627cde136a2ec5ebf4088a32f52abec5 [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;
52
53 private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions;
54 private ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
55 private int mSuggestionCount;
56 private final boolean mCountryMode;
57 private LayoutInflater mInflater;
58
Mihai Nita137b96e2016-01-25 11:31:15 -080059 public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
Mihai Nita1808ff72016-01-12 08:53:54 -080060 mCountryMode = countryMode;
61 mLocaleOptions = new ArrayList<>(localeOptions.size());
62 for (LocaleStore.LocaleInfo li : localeOptions) {
63 if (li.isSuggested()) {
64 mSuggestionCount++;
65 }
66 mLocaleOptions.add(li);
67 }
68 }
69
70 @Override
71 public boolean areAllItemsEnabled() {
72 return false;
73 }
74
75 @Override
76 public boolean isEnabled(int position) {
77 return getItemViewType(position) == TYPE_LOCALE;
78 }
79
80 @Override
81 public int getItemViewType(int position) {
82 if (!showHeaders()) {
83 return TYPE_LOCALE;
84 } else {
85 if (position == 0) {
86 return TYPE_HEADER_SUGGESTED;
87 }
88 if (position == mSuggestionCount + 1) {
89 return TYPE_HEADER_ALL_OTHERS;
90 }
91 return TYPE_LOCALE;
92 }
93 }
94
95 @Override
96 public int getViewTypeCount() {
97 if (showHeaders()) {
98 return 3; // Two headers in addition to the locales
99 } else {
100 return 1; // Locales items only
101 }
102 }
103
104 @Override
105 public int getCount() {
106 if (showHeaders()) {
107 return mLocaleOptions.size() + 2; // 2 extra for the headers
108 } else {
109 return mLocaleOptions.size();
110 }
111 }
112
113 @Override
114 public Object getItem(int position) {
115 int offset = 0;
116 if (showHeaders()) {
117 offset = position > mSuggestionCount ? -2 : -1;
118 }
119
120 return mLocaleOptions.get(position + offset);
121 }
122
123 @Override
124 public long getItemId(int position) {
125 return position;
126 }
127
128 @Override
129 public View getView(int position, View convertView, ViewGroup parent) {
130 if (convertView == null && mInflater == null) {
131 mInflater = LayoutInflater.from(parent.getContext());
132 }
133
134 int itemType = getItemViewType(position);
135 switch (itemType) {
136 case TYPE_HEADER_SUGGESTED: // intentional fallthrough
137 case TYPE_HEADER_ALL_OTHERS:
138 // Covers both null, and "reusing" a wrong kind of view
139 if (!(convertView instanceof TextView)) {
140 convertView = mInflater.inflate(R.layout.language_picker_section_header,
141 parent, false);
142 }
143 TextView textView = (TextView) convertView;
144 if (itemType == TYPE_HEADER_SUGGESTED) {
145 textView.setText(R.string.language_picker_section_suggested);
146 } else {
147 textView.setText(R.string.language_picker_section_all);
148 }
149 textView.setTextLocale(Locale.getDefault());
150 break;
151 default:
152 // Covers both null, and "reusing" a wrong kind of view
153 if (!(convertView instanceof ViewGroup)) {
154 convertView = mInflater.inflate(R.layout.language_picker_item, parent, false);
155 }
156
157 TextView text = (TextView) convertView.findViewById(R.id.locale);
Mihai Nita1808ff72016-01-12 08:53:54 -0800158 LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800159 text.setText(item.getLabel(mCountryMode));
Mihai Nita502141d2016-02-22 16:40:26 -0800160 text.setTextLocale(item.getLocale());
Mihai Nita24215ec2016-01-29 12:32:56 -0800161 if (mCountryMode) {
162 int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
163 //noinspection ResourceType
164 convertView.setLayoutDirection(layoutDir);
165 text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
166 ? View.TEXT_DIRECTION_RTL
167 : View.TEXT_DIRECTION_LTR);
168 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800169 }
170 return convertView;
171 }
172
Mihai Nita1808ff72016-01-12 08:53:54 -0800173 private boolean showHeaders() {
Mihai Nitaf1f39cf2016-02-29 14:42:12 -0800174 if (mCountryMode) { // never show suggestions in country mode
175 return false;
176 }
Mihai Nita1808ff72016-01-12 08:53:54 -0800177 return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size();
178 }
179
Mihai Nita137b96e2016-01-25 11:31:15 -0800180 /**
181 * Sorts the items in the adapter using a locale-aware comparator.
182 * @param comp The locale-aware comparator to use.
183 */
Mihai Nita1808ff72016-01-12 08:53:54 -0800184 public void sort(LocaleHelper.LocaleInfoComparator comp) {
185 Collections.sort(mLocaleOptions, comp);
186 }
187
188 class FilterByNativeAndUiNames extends Filter {
189
190 @Override
191 protected FilterResults performFiltering(CharSequence prefix) {
192 FilterResults results = new FilterResults();
193
194 if (mOriginalLocaleOptions == null) {
195 mOriginalLocaleOptions = new ArrayList<>(mLocaleOptions);
196 }
197
198 ArrayList<LocaleStore.LocaleInfo> values;
199 values = new ArrayList<>(mOriginalLocaleOptions);
200 if (prefix == null || prefix.length() == 0) {
201 results.values = values;
202 results.count = values.size();
203 } else {
204 // TODO: decide if we should use the string's locale
205 Locale locale = Locale.getDefault();
206 String prefixString = LocaleHelper.normalizeForSearch(prefix.toString(), locale);
207
208 final int count = values.size();
209 final ArrayList<LocaleStore.LocaleInfo> newValues = new ArrayList<>();
210
211 for (int i = 0; i < count; i++) {
212 final LocaleStore.LocaleInfo value = values.get(i);
213 final String nameToCheck = LocaleHelper.normalizeForSearch(
214 value.getFullNameInUiLanguage(), locale);
215 final String nativeNameToCheck = LocaleHelper.normalizeForSearch(
216 value.getFullNameNative(), locale);
217 if (wordMatches(nativeNameToCheck, prefixString)
218 || wordMatches(nameToCheck, prefixString)) {
219 newValues.add(value);
220 }
221 }
222
223 results.values = newValues;
224 results.count = newValues.size();
225 }
226
227 return results;
228 }
229
230 // TODO: decide if this is enough, or we want to use a BreakIterator...
231 boolean wordMatches(String valueText, String prefixString) {
Mihai Nita137b96e2016-01-25 11:31:15 -0800232 // First match against the whole, non-split value
Mihai Nita1808ff72016-01-12 08:53:54 -0800233 if (valueText.startsWith(prefixString)) {
234 return true;
235 }
236
237 final String[] words = valueText.split(" ");
238 // Start at index 0, in case valueText starts with space(s)
239 for (String word : words) {
240 if (word.startsWith(prefixString)) {
241 return true;
242 }
243 }
244
245 return false;
246 }
247
248 @Override
Mihai Nita1808ff72016-01-12 08:53:54 -0800249 protected void publishResults(CharSequence constraint, FilterResults results) {
250 mLocaleOptions = (ArrayList<LocaleStore.LocaleInfo>) results.values;
251
252 mSuggestionCount = 0;
253 for (LocaleStore.LocaleInfo li : mLocaleOptions) {
254 if (li.isSuggested()) {
255 mSuggestionCount++;
256 }
257 }
258
259 if (results.count > 0) {
260 notifyDataSetChanged();
261 } else {
262 notifyDataSetInvalidated();
263 }
264 }
265 }
266
267 @Override
268 public Filter getFilter() {
269 return new FilterByNativeAndUiNames();
270 }
271}