Home | History | Annotate | Download | only in worldclock
      1 /*
      2  * Copyright (C) 2012 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.deskclock.worldclock;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Activity;
     21 import android.content.ActivityNotFoundException;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.SharedPreferences;
     25 import android.os.Bundle;
     26 import android.preference.PreferenceManager;
     27 import android.text.TextUtils;
     28 import android.text.format.DateFormat;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuItem;
     32 import android.view.View;
     33 import android.view.View.OnClickListener;
     34 import android.view.ViewGroup;
     35 import android.view.inputmethod.EditorInfo;
     36 import android.widget.BaseAdapter;
     37 import android.widget.CheckBox;
     38 import android.widget.CompoundButton;
     39 import android.widget.CompoundButton.OnCheckedChangeListener;
     40 import android.widget.Filter;
     41 import android.widget.Filterable;
     42 import android.widget.ImageView;
     43 import android.widget.ListView;
     44 import android.widget.SearchView;
     45 import android.widget.SearchView.OnQueryTextListener;
     46 import android.widget.SectionIndexer;
     47 import android.widget.TextView;
     48 
     49 import com.android.deskclock.DeskClock;
     50 import com.android.deskclock.R;
     51 import com.android.deskclock.SettingsActivity;
     52 import com.android.deskclock.Utils;
     53 
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.Calendar;
     57 import java.util.Collection;
     58 import java.util.HashMap;
     59 import java.util.List;
     60 import java.util.Locale;
     61 import java.util.TimeZone;
     62 
     63 /**
     64  * Cities chooser for the world clock
     65  */
     66 public class CitiesActivity extends Activity implements OnCheckedChangeListener,
     67         View.OnClickListener, OnQueryTextListener {
     68 
     69     private static final String KEY_SEARCH_QUERY = "search_query";
     70     private static final String KEY_SEARCH_MODE = "search_mode";
     71     private static final String KEY_LIST_POSITION = "list_position";
     72 
     73     private static final String PREF_SORT = "sort_preference";
     74 
     75     private static final int SORT_BY_NAME = 0;
     76     private static final int SORT_BY_GMT_OFFSET = 1;
     77 
     78     /**
     79      * This must be false for production. If true, turns on logging, test code,
     80      * etc.
     81      */
     82     static final boolean DEBUG = false;
     83     static final String TAG = "CitiesActivity";
     84 
     85     private LayoutInflater mFactory;
     86     private ListView mCitiesList;
     87     private CityAdapter mAdapter;
     88     private HashMap<String, CityObj> mUserSelectedCities;
     89     private Calendar mCalendar;
     90 
     91     private SearchView mSearchView;
     92     private StringBuffer mQueryTextBuffer = new StringBuffer();
     93     private boolean mSearchMode;
     94     private int mPosition = -1;
     95 
     96     private SharedPreferences mPrefs;
     97     private int mSortType;
     98 
     99     private String mSelectedCitiesHeaderString;
    100 
    101     /***
    102      * Adapter for a list of cities with the respected time zone. The Adapter
    103      * sorts the list alphabetically and create an indexer.
    104      ***/
    105     private class CityAdapter extends BaseAdapter implements Filterable, SectionIndexer {
    106         private static final int VIEW_TYPE_CITY = 0;
    107         private static final int VIEW_TYPE_HEADER = 1;
    108 
    109         private static final String DELETED_ENTRY = "C0";
    110 
    111         private List<CityObj> mDisplayedCitiesList;
    112 
    113         private CityObj[] mCities;
    114         private CityObj[] mSelectedCities;
    115 
    116         private final int mLayoutDirection;
    117 
    118         // A map that caches names of cities in local memory.  The names in this map are
    119         // preferred over the names of the selected cities stored in SharedPreferences, which could
    120         // be in a different language.  This map gets reloaded on a locale change, when the new
    121         // language's city strings are read from the xml file.
    122         private HashMap<String, String> mCityNameMap = new HashMap<String, String>();
    123 
    124         private String[] mSectionHeaders;
    125         private Integer[] mSectionPositions;
    126 
    127         private CityNameComparator mSortByNameComparator = new CityNameComparator();
    128         private CityGmtOffsetComparator mSortByTimeComparator = new CityGmtOffsetComparator();
    129 
    130         private final LayoutInflater mInflater;
    131         private boolean mIs24HoursMode; // AM/PM or 24 hours mode
    132 
    133         private final String mPattern12;
    134         private final String mPattern24;
    135 
    136         private int mSelectedEndPosition = 0;
    137 
    138         private Filter mFilter = new Filter() {
    139 
    140             @Override
    141             protected synchronized FilterResults performFiltering(CharSequence constraint) {
    142                 FilterResults results = new FilterResults();
    143                 String modifiedQuery = constraint.toString().trim().toUpperCase();
    144 
    145                 ArrayList<CityObj> filteredList = new ArrayList<CityObj>();
    146                 ArrayList<String> sectionHeaders = new ArrayList<String>();
    147                 ArrayList<Integer> sectionPositions = new ArrayList<Integer>();
    148 
    149                 // If the search query is empty, add in the selected cities
    150                 if (TextUtils.isEmpty(modifiedQuery) && mSelectedCities != null) {
    151                     if (mSelectedCities.length > 0) {
    152                         sectionHeaders.add("+");
    153                         sectionPositions.add(0);
    154                         filteredList.add(new CityObj(mSelectedCitiesHeaderString,
    155                                 mSelectedCitiesHeaderString,
    156                                 null));
    157                     }
    158                     for (CityObj city : mSelectedCities) {
    159                         filteredList.add(city);
    160                     }
    161                 }
    162 
    163                 mSelectedEndPosition = filteredList.size();
    164 
    165                 long currentTime = System.currentTimeMillis();
    166                 String val = null;
    167                 int offset = -100000; //some value that cannot be a real offset
    168                 for (CityObj city : mCities) {
    169 
    170                     // If the city is a deleted entry, ignore it.
    171                     if (city.mCityId.equals(DELETED_ENTRY)) {
    172                         continue;
    173                     }
    174 
    175                     // If the search query is empty, add section headers.
    176                     if (TextUtils.isEmpty(modifiedQuery)) {
    177 
    178 
    179                         // If the list is sorted by name, and the city begins with a letter
    180                         // different than the previous city's letter, insert a section header.
    181                         if (mSortType == SORT_BY_NAME
    182                                 && !city.mCityName.substring(0, 1).equals(val)) {
    183                                 val = city.mCityName.substring(0, 1).toUpperCase();
    184                                 sectionHeaders.add(val);
    185                                 sectionPositions.add(filteredList.size());
    186                                 filteredList.add(new CityObj(val, null, null));
    187                         }
    188 
    189                         // If the list is sorted by time, and the gmt offset is different than
    190                         // the previous city's gmt offset, insert a section header.
    191                         if (mSortType == SORT_BY_GMT_OFFSET) {
    192                             TimeZone timezone = TimeZone.getTimeZone(city.mTimeZone);
    193                             int newOffset = timezone.getOffset(currentTime);
    194                             if (offset != newOffset) {
    195                                 offset = newOffset;
    196                                 String offsetString = Utils.getGMTHourOffset(timezone, true);
    197                                 sectionHeaders.add(offsetString);
    198                                 sectionPositions.add(filteredList.size());
    199                                 filteredList.add(new CityObj(null, offsetString, null));
    200                             }
    201                         }
    202                     }
    203 
    204                     // If the city name begins with the query, add the city into the list.
    205                     // If the query is empty, the city will automatically be added to the list.
    206                     String cityName = city.mCityName.trim().toUpperCase();
    207                     if (city.mCityId != null && cityName.startsWith(modifiedQuery)) {
    208                         filteredList.add(city);
    209                     }
    210                 }
    211 
    212                 mSectionHeaders = sectionHeaders.toArray(new String[sectionHeaders.size()]);
    213                 mSectionPositions = sectionPositions.toArray(new Integer[sectionPositions.size()]);
    214 
    215                 results.values = filteredList;
    216                 results.count = filteredList.size();
    217                 return results;
    218             }
    219 
    220             @Override
    221             protected void publishResults(CharSequence constraint, FilterResults results) {
    222                 mDisplayedCitiesList = (ArrayList<CityObj>) results.values;
    223                 if (mPosition >= 0) {
    224                     mCitiesList.setSelectionFromTop(mPosition, 0);
    225                     mPosition = -1;
    226                 }
    227                 notifyDataSetChanged();
    228             }
    229         };
    230 
    231         public CityAdapter(
    232                 Context context, LayoutInflater factory) {
    233             super();
    234             mCalendar = Calendar.getInstance();
    235             mCalendar.setTimeInMillis(System.currentTimeMillis());
    236             mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
    237             mInflater = factory;
    238 
    239             // Load the cities from xml.
    240             mCities = Utils.loadCitiesFromXml(context);
    241 
    242             // Reload the city name map with the recently parsed city names of the currently
    243             // selected language for use with selected cities.
    244             mCityNameMap.clear();
    245             for (CityObj city : mCities) {
    246                 mCityNameMap.put(city.mCityId, city.mCityName);
    247             }
    248 
    249             // Re-organize the selected cities into an array.
    250             Collection<CityObj> selectedCities = mUserSelectedCities.values();
    251             mSelectedCities = selectedCities.toArray(new CityObj[selectedCities.size()]);
    252 
    253             // Override the selected city names in the shared preferences with the
    254             // city names in the updated city name map, which will always reflect the
    255             // current language.
    256             for (CityObj city : mSelectedCities) {
    257                 String newCityName = mCityNameMap.get(city.mCityId);
    258                 if (newCityName != null) {
    259                     city.mCityName = newCityName;
    260                 }
    261             }
    262 
    263             mPattern24 = DateFormat.getBestDateTimePattern(Locale.getDefault(), "Hm");
    264 
    265             // There's an RTL layout bug that causes jank when fast-scrolling through
    266             // the list in 12-hour mode in an RTL locale. We can work around this by
    267             // ensuring the strings are the same length by using "hh" instead of "h".
    268             String pattern12 = DateFormat.getBestDateTimePattern(Locale.getDefault(), "hma");
    269             if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
    270                 pattern12 = pattern12.replaceAll("h", "hh");
    271             }
    272             mPattern12 = pattern12;
    273 
    274             sortCities(mSortType);
    275             set24HoursMode(context);
    276         }
    277 
    278         public void refreshSelectedCities() {
    279             Collection<CityObj> selectedCities = mUserSelectedCities.values();
    280             mSelectedCities = selectedCities.toArray(new CityObj[selectedCities.size()]);
    281             sortCities(mSortType);
    282         }
    283 
    284         public void toggleSort() {
    285             if (mSortType == SORT_BY_NAME) {
    286                 sortCities(SORT_BY_GMT_OFFSET);
    287             } else {
    288                 sortCities(SORT_BY_NAME);
    289             }
    290         }
    291 
    292         private void sortCities(final int sortType) {
    293             mSortType = sortType;
    294             Arrays.sort(mCities, sortType == SORT_BY_NAME ? mSortByNameComparator
    295                     : mSortByTimeComparator);
    296             if (mSelectedCities != null) {
    297                 Arrays.sort(mSelectedCities, sortType == SORT_BY_NAME ? mSortByNameComparator
    298                         : mSortByTimeComparator);
    299             }
    300             mPrefs.edit().putInt(PREF_SORT, sortType).commit();
    301             mFilter.filter(mQueryTextBuffer.toString());
    302         }
    303 
    304         @Override
    305         public int getCount() {
    306             return mDisplayedCitiesList != null ? mDisplayedCitiesList.size() : 0;
    307         }
    308 
    309         @Override
    310         public Object getItem(int p) {
    311             if (mDisplayedCitiesList != null && p >= 0 && p < mDisplayedCitiesList.size()) {
    312                 return mDisplayedCitiesList.get(p);
    313             }
    314             return null;
    315         }
    316 
    317         @Override
    318         public long getItemId(int p) {
    319             return p;
    320         }
    321 
    322         @Override
    323         public boolean isEnabled(int p) {
    324             return mDisplayedCitiesList != null && mDisplayedCitiesList.get(p).mCityId != null;
    325         }
    326 
    327         @Override
    328         public synchronized View getView(int position, View view, ViewGroup parent) {
    329             if (mDisplayedCitiesList == null || position < 0
    330                     || position >= mDisplayedCitiesList.size()) {
    331                 return null;
    332             }
    333             CityObj c = mDisplayedCitiesList.get(position);
    334             // Header view: A CityObj with nothing but the first letter as the name
    335             if (c.mCityId == null) {
    336                 if (view == null) {
    337                     view = mInflater.inflate(R.layout.city_list_header, parent, false);
    338                     view.setTag(view.findViewById(R.id.header));
    339                 }
    340                 ((TextView) view.getTag()).setText(
    341                         mSortType == SORT_BY_NAME ? c.mCityName : c.mTimeZone);
    342             } else { // City view
    343                 // Make sure to recycle a City view only
    344                 if (view == null) {
    345                     view = mInflater.inflate(R.layout.city_list_item, parent, false);
    346                     final CityViewHolder holder = new CityViewHolder();
    347                     holder.name = (TextView) view.findViewById(R.id.city_name);
    348                     holder.time = (TextView) view.findViewById(R.id.city_time);
    349                     holder.selected = (CheckBox) view.findViewById(R.id.city_onoff);
    350                     holder.selectedPin = (ImageView) view.findViewById(R.id.city_selected_icon);
    351                     holder.remove = (ImageView) view.findViewById(R.id.city_remove);
    352                     holder.remove.setOnClickListener(new OnClickListener() {
    353 
    354                         @Override
    355                         public void onClick(View view) {
    356                             CompoundButton b = holder.selected;
    357                             onCheckedChanged(b, false);
    358                             b.setChecked(false);
    359                             mAdapter.refreshSelectedCities();
    360                         }
    361                     });
    362                     view.setTag(holder);
    363                 }
    364                 view.setOnClickListener(CitiesActivity.this);
    365                 CityViewHolder holder = (CityViewHolder) view.getTag();
    366 
    367                 if (position < mSelectedEndPosition) {
    368                     holder.selected.setVisibility(View.GONE);
    369                     holder.time.setVisibility(View.GONE);
    370                     holder.remove.setVisibility(View.VISIBLE);
    371                     holder.selectedPin.setVisibility(View.VISIBLE);
    372                     view.setEnabled(false);
    373                 } else {
    374                     holder.selected.setVisibility(View.VISIBLE);
    375                     holder.time.setVisibility(View.VISIBLE);
    376                     holder.remove.setVisibility(View.GONE);
    377                     holder.selectedPin.setVisibility(View.GONE);
    378                     view.setEnabled(true);
    379                 }
    380                 holder.selected.setTag(c);
    381                 holder.selected.setChecked(mUserSelectedCities.containsKey(c.mCityId));
    382                 holder.selected.setOnCheckedChangeListener(CitiesActivity.this);
    383                 holder.name.setText(c.mCityName, TextView.BufferType.SPANNABLE);
    384                 holder.time.setText(getTimeCharSequence(c.mTimeZone));
    385             }
    386             return view;
    387         }
    388 
    389         private CharSequence getTimeCharSequence(String timeZone) {
    390             mCalendar.setTimeZone(TimeZone.getTimeZone(timeZone));
    391             return DateFormat.format(mIs24HoursMode ? mPattern24 : mPattern12, mCalendar);
    392         }
    393 
    394         @Override
    395         public int getViewTypeCount() {
    396             return 2;
    397         }
    398 
    399         @Override
    400         public int getItemViewType(int position) {
    401             return (mDisplayedCitiesList.get(position).mCityId != null)
    402                     ? VIEW_TYPE_CITY : VIEW_TYPE_HEADER;
    403         }
    404 
    405         private class CityViewHolder {
    406             TextView name;
    407             TextView time;
    408             CheckBox selected;
    409             ImageView selectedPin;
    410             ImageView remove;
    411         }
    412 
    413         public void set24HoursMode(Context c) {
    414             mIs24HoursMode = DateFormat.is24HourFormat(c);
    415             notifyDataSetChanged();
    416         }
    417 
    418         @Override
    419         public int getPositionForSection(int section) {
    420             return !isEmpty(mSectionPositions) ? mSectionPositions[section] : 0;
    421         }
    422 
    423 
    424         @Override
    425         public int getSectionForPosition(int p) {
    426             final Integer[] positions = mSectionPositions;
    427             if (!isEmpty(positions)) {
    428                 for (int i = 0; i < positions.length - 1; i++) {
    429                     if (p >= positions[i]
    430                             && p < positions[i + 1]) {
    431                         return i;
    432                     }
    433                 }
    434                 if (p >= positions[positions.length - 1]) {
    435                     return positions.length - 1;
    436                 }
    437             }
    438             return 0;
    439         }
    440 
    441         @Override
    442         public Object[] getSections() {
    443             return mSectionHeaders;
    444         }
    445 
    446         @Override
    447         public Filter getFilter() {
    448             return mFilter;
    449         }
    450 
    451         private boolean isEmpty(Object[] array) {
    452             return array == null || array.length == 0;
    453         }
    454     }
    455 
    456     @Override
    457     protected void onCreate(Bundle savedInstanceState) {
    458         super.onCreate(savedInstanceState);
    459         mFactory = LayoutInflater.from(this);
    460         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    461         mSortType = mPrefs.getInt(PREF_SORT, SORT_BY_NAME);
    462         mSelectedCitiesHeaderString = getString(R.string.selected_cities_label);
    463         if (savedInstanceState != null) {
    464             mQueryTextBuffer.append(savedInstanceState.getString(KEY_SEARCH_QUERY));
    465             mSearchMode = savedInstanceState.getBoolean(KEY_SEARCH_MODE);
    466             mPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
    467         }
    468         updateLayout();
    469     }
    470 
    471     @Override
    472     public void onSaveInstanceState(Bundle bundle) {
    473         super.onSaveInstanceState(bundle);
    474         bundle.putString(KEY_SEARCH_QUERY, mQueryTextBuffer.toString());
    475         bundle.putBoolean(KEY_SEARCH_MODE, mSearchMode);
    476         bundle.putInt(KEY_LIST_POSITION, mCitiesList.getFirstVisiblePosition());
    477     }
    478 
    479     private void updateLayout() {
    480         setContentView(R.layout.cities_activity);
    481         mCitiesList = (ListView) findViewById(R.id.cities_list);
    482         setFastScroll(TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
    483         mCitiesList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
    484         mCitiesList.setFastScrollEnabled(true);
    485         mUserSelectedCities = Cities.readCitiesFromSharedPrefs(
    486                 PreferenceManager.getDefaultSharedPreferences(this));
    487         mAdapter = new CityAdapter(this, mFactory);
    488         mCitiesList.setAdapter(mAdapter);
    489         ActionBar actionBar = getActionBar();
    490         if (actionBar != null) {
    491             actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
    492         }
    493     }
    494 
    495     private void setFastScroll(boolean enabled) {
    496         if (mCitiesList != null) {
    497             mCitiesList.setFastScrollAlwaysVisible(enabled);
    498             mCitiesList.setFastScrollEnabled(enabled);
    499         }
    500     }
    501 
    502     @Override
    503     public void onResume() {
    504         super.onResume();
    505         if (mAdapter != null) {
    506             mAdapter.set24HoursMode(this);
    507         }
    508     }
    509 
    510     @Override
    511     public void onPause() {
    512         super.onPause();
    513         Cities.saveCitiesToSharedPrefs(PreferenceManager.getDefaultSharedPreferences(this),
    514                 mUserSelectedCities);
    515         Intent i = new Intent(Cities.WORLDCLOCK_UPDATE_INTENT);
    516         sendBroadcast(i);
    517     }
    518 
    519     @Override
    520     public boolean onOptionsItemSelected(MenuItem item) {
    521         switch (item.getItemId()) {
    522             case R.id.menu_item_settings:
    523                 startActivity(new Intent(this, SettingsActivity.class));
    524                 return true;
    525             case R.id.menu_item_help:
    526                 Intent i = item.getIntent();
    527                 if (i != null) {
    528                     try {
    529                         startActivity(i);
    530                     } catch (ActivityNotFoundException e) {
    531                         // No activity found to match the intent - ignore
    532                     }
    533                 }
    534                 return true;
    535             case R.id.menu_item_sort:
    536                 if (mAdapter != null) {
    537                     mAdapter.toggleSort();
    538                     setFastScroll(TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
    539                 }
    540                 return true;
    541             case android.R.id.home:
    542                 Intent intent = new Intent(this, DeskClock.class);
    543                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    544                 startActivity(intent);
    545                 return true;
    546             default:
    547                 break;
    548         }
    549         return super.onOptionsItemSelected(item);
    550     }
    551 
    552     @Override
    553     public boolean onCreateOptionsMenu(Menu menu) {
    554         getMenuInflater().inflate(R.menu.cities_menu, menu);
    555         MenuItem help = menu.findItem(R.id.menu_item_help);
    556         if (help != null) {
    557             Utils.prepareHelpMenuItem(this, help);
    558         }
    559 
    560         MenuItem searchMenu = menu.findItem(R.id.menu_item_search);
    561         mSearchView = (SearchView) searchMenu.getActionView();
    562         mSearchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
    563         mSearchView.setOnSearchClickListener(new OnClickListener() {
    564 
    565             @Override
    566             public void onClick(View arg0) {
    567                 mSearchMode = true;
    568             }
    569         });
    570         mSearchView.setOnCloseListener(new SearchView.OnCloseListener() {
    571 
    572             @Override
    573             public boolean onClose() {
    574                 mSearchMode = false;
    575                 return false;
    576             }
    577         });
    578         if (mSearchView != null) {
    579             mSearchView.setOnQueryTextListener(this);
    580             mSearchView.setQuery(mQueryTextBuffer.toString(), false);
    581             if (mSearchMode) {
    582                 mSearchView.requestFocus();
    583                 mSearchView.setIconified(false);
    584             }
    585         }
    586         return super.onCreateOptionsMenu(menu);
    587     }
    588 
    589     @Override
    590     public boolean onPrepareOptionsMenu(Menu menu) {
    591         MenuItem sortMenuItem = menu.findItem(R.id.menu_item_sort);
    592         if (mSortType == SORT_BY_NAME) {
    593             sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_gmt_offset));
    594         } else {
    595             sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_name));
    596         }
    597         return super.onPrepareOptionsMenu(menu);
    598     }
    599 
    600     @Override
    601     public void onCheckedChanged(CompoundButton b, boolean checked) {
    602         CityObj c = (CityObj) b.getTag();
    603         if (checked) {
    604             mUserSelectedCities.put(c.mCityId, c);
    605         } else {
    606             mUserSelectedCities.remove(c.mCityId);
    607         }
    608     }
    609 
    610     @Override
    611     public void onClick(View v) {
    612         CompoundButton b = (CompoundButton) v.findViewById(R.id.city_onoff);
    613         boolean checked = b.isChecked();
    614         onCheckedChanged(b, checked);
    615         b.setChecked(!checked);
    616         mAdapter.refreshSelectedCities();
    617     }
    618 
    619     @Override
    620     public boolean onQueryTextChange(String queryText) {
    621         mQueryTextBuffer.setLength(0);
    622         mQueryTextBuffer.append(queryText);
    623         mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
    624         mAdapter.getFilter().filter(queryText);
    625         return true;
    626     }
    627 
    628     @Override
    629     public boolean onQueryTextSubmit(String arg0) {
    630         return false;
    631     }
    632 }
    633