Home | History | Annotate | Download | only in app
      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.text.TextUtils;
     20 import android.view.LayoutInflater;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 import android.widget.BaseAdapter;
     24 import android.widget.Filter;
     25 import android.widget.Filterable;
     26 import android.widget.TextView;
     27 
     28 import com.android.internal.R;
     29 
     30 import java.util.ArrayList;
     31 import java.util.Collections;
     32 import java.util.Locale;
     33 import 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  */
     48 public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
     49     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     private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6;
     53 
     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 
     60     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
     61         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                     if (mCountryMode) {
    149                         textView.setText(R.string.region_picker_section_all);
    150                     } else {
    151                         textView.setText(R.string.language_picker_section_all);
    152                     }
    153                 }
    154                 textView.setTextLocale(Locale.getDefault());
    155                 break;
    156             default:
    157                 // Covers both null, and "reusing" a wrong kind of view
    158                 if (!(convertView instanceof ViewGroup)) {
    159                     convertView = mInflater.inflate(R.layout.language_picker_item, parent, false);
    160                 }
    161 
    162                 TextView text = (TextView) convertView.findViewById(R.id.locale);
    163                 LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
    164                 text.setText(item.getLabel(mCountryMode));
    165                 text.setTextLocale(item.getLocale());
    166                 text.setContentDescription(item.getContentDescription(mCountryMode));
    167                 if (mCountryMode) {
    168                     int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
    169                     //noinspection ResourceType
    170                     convertView.setLayoutDirection(layoutDir);
    171                     text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
    172                             ? View.TEXT_DIRECTION_RTL
    173                             : View.TEXT_DIRECTION_LTR);
    174                 }
    175         }
    176         return convertView;
    177     }
    178 
    179     private boolean showHeaders() {
    180         // We don't want to show suggestions for locales with very few regions
    181         // (e.g. Romanian, with 2 regions)
    182         // So we put a (somewhat) arbitrary limit.
    183         //
    184         // The initial idea was to make that limit dependent on the screen height.
    185         // But that would mean rotating the screen could make the suggestions disappear,
    186         // as the number of countries that fits on the screen would be different in portrait
    187         // and landscape mode.
    188         if (mCountryMode && mLocaleOptions.size() < MIN_REGIONS_FOR_SUGGESTIONS) {
    189             return false;
    190         }
    191         return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size();
    192     }
    193 
    194     /**
    195      * Sorts the items in the adapter using a locale-aware comparator.
    196      * @param comp The locale-aware comparator to use.
    197      */
    198     public void sort(LocaleHelper.LocaleInfoComparator comp) {
    199         Collections.sort(mLocaleOptions, comp);
    200     }
    201 
    202     class FilterByNativeAndUiNames extends Filter {
    203 
    204         @Override
    205         protected FilterResults performFiltering(CharSequence prefix) {
    206             FilterResults results = new FilterResults();
    207 
    208             if (mOriginalLocaleOptions == null) {
    209                 mOriginalLocaleOptions = new ArrayList<>(mLocaleOptions);
    210             }
    211 
    212             ArrayList<LocaleStore.LocaleInfo> values;
    213             values = new ArrayList<>(mOriginalLocaleOptions);
    214             if (prefix == null || prefix.length() == 0) {
    215                 results.values = values;
    216                 results.count = values.size();
    217             } else {
    218                 // TODO: decide if we should use the string's locale
    219                 Locale locale = Locale.getDefault();
    220                 String prefixString = LocaleHelper.normalizeForSearch(prefix.toString(), locale);
    221 
    222                 final int count = values.size();
    223                 final ArrayList<LocaleStore.LocaleInfo> newValues = new ArrayList<>();
    224 
    225                 for (int i = 0; i < count; i++) {
    226                     final LocaleStore.LocaleInfo value = values.get(i);
    227                     final String nameToCheck = LocaleHelper.normalizeForSearch(
    228                             value.getFullNameInUiLanguage(), locale);
    229                     final String nativeNameToCheck = LocaleHelper.normalizeForSearch(
    230                             value.getFullNameNative(), locale);
    231                     if (wordMatches(nativeNameToCheck, prefixString)
    232                             || wordMatches(nameToCheck, prefixString)) {
    233                         newValues.add(value);
    234                     }
    235                 }
    236 
    237                 results.values = newValues;
    238                 results.count = newValues.size();
    239             }
    240 
    241             return results;
    242         }
    243 
    244         // TODO: decide if this is enough, or we want to use a BreakIterator...
    245         boolean wordMatches(String valueText, String prefixString) {
    246             // First match against the whole, non-split value
    247             if (valueText.startsWith(prefixString)) {
    248                 return true;
    249             }
    250 
    251             final String[] words = valueText.split(" ");
    252             // Start at index 0, in case valueText starts with space(s)
    253             for (String word : words) {
    254                 if (word.startsWith(prefixString)) {
    255                     return true;
    256                 }
    257             }
    258 
    259             return false;
    260         }
    261 
    262         @Override
    263         protected void publishResults(CharSequence constraint, FilterResults results) {
    264             mLocaleOptions = (ArrayList<LocaleStore.LocaleInfo>) results.values;
    265 
    266             mSuggestionCount = 0;
    267             for (LocaleStore.LocaleInfo li : mLocaleOptions) {
    268                 if (li.isSuggested()) {
    269                     mSuggestionCount++;
    270                 }
    271             }
    272 
    273             if (results.count > 0) {
    274                 notifyDataSetChanged();
    275             } else {
    276                 notifyDataSetInvalidated();
    277             }
    278         }
    279     }
    280 
    281     @Override
    282     public Filter getFilter() {
    283         return new FilterByNativeAndUiNames();
    284     }
    285 }
    286