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
17package com.android.deskclock.worldclock;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.os.Bundle;
26import android.preference.PreferenceManager;
27import android.text.format.DateFormat;
28import android.view.LayoutInflater;
29import android.view.Menu;
30import android.view.MenuItem;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.BaseAdapter;
34import android.widget.CheckBox;
35import android.widget.CompoundButton;
36import android.widget.CompoundButton.OnCheckedChangeListener;
37import android.widget.ListView;
38import android.widget.SectionIndexer;
39import android.widget.TextView;
40
41import com.android.deskclock.Alarms;
42import com.android.deskclock.DeskClock;
43import com.android.deskclock.Log;
44import com.android.deskclock.R;
45import com.android.deskclock.SettingsActivity;
46import com.android.deskclock.Utils;
47
48import java.text.Collator;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.Calendar;
52import java.util.Comparator;
53import java.util.HashMap;
54import java.util.TimeZone;
55
56/**
57 * Cities chooser for the world clock
58 */
59public class CitiesActivity extends Activity implements OnCheckedChangeListener, View.OnClickListener {
60
61    /** This must be false for production.  If true, turns on logging,
62        test code, etc. */
63    static final boolean DEBUG = false;
64    static final String TAG = "CitiesActivity";
65
66    private LayoutInflater mFactory;
67    private ListView mCitiesList;
68    private CityAdapter mAdapter;
69    private HashMap<String, CityObj> mUserSelectedCities;
70    private Calendar mCalendar;
71    private final Collator mCollator = Collator.getInstance();
72
73
74/***
75* Adapter for a list of cities with the respected time zone.
76* The Adapter sorts the list alphabetically and create an indexer.
77***/
78
79    private class CityAdapter extends BaseAdapter implements SectionIndexer {
80        private static final String DELETED_ENTRY = "C0";
81        private Object [] mAllTheCitiesList;                      // full list of the cities
82        private final HashMap<String, CityObj> mSelectedCitiesList; // Selected cities by the use
83        private final LayoutInflater mInflater;
84        private boolean mIs24HoursMode;                            // AM/PM or 24 hours mode
85        private Object [] mSectionHeaders;
86        private Object [] mSectionPositions;
87
88        public CityAdapter(
89                Context context,  HashMap<String, CityObj> selectedList, LayoutInflater factory) {
90            super();
91            loadCitiesDataBase(context);
92            mSelectedCitiesList = selectedList;
93            mInflater = factory;
94            mCalendar = Calendar.getInstance();
95            mCalendar.setTimeInMillis(System.currentTimeMillis());
96            set24HoursMode(context);
97        }
98
99        @Override
100        public int getCount() {
101            return mAllTheCitiesList.length;
102        }
103
104        @Override
105        public Object getItem(int p) {
106            if (p >=0 && p < mAllTheCitiesList.length) {
107                return mAllTheCitiesList [p];
108            }
109            return null;
110        }
111
112        @Override
113        public long getItemId(int p) {
114            return p;
115        }
116
117        @Override
118        public boolean isEnabled(int p) {
119            return ((CityObj)mAllTheCitiesList[p]).mCityId != null;
120        }
121
122        @Override
123        public View getView(int position, View view, ViewGroup parent) {
124            if (position < 0 || position >=  mAllTheCitiesList.length) {
125                return null;
126            }
127            CityObj c = (CityObj)mAllTheCitiesList [position];
128            // Header view (A CityObj with nothing but the first letter as the name
129            if (c.mCityId == null) {
130                if (view == null || view.findViewById(R.id.header) == null) {
131                    view =  mInflater.inflate(R.layout.city_list_header, parent, false);
132                }
133                TextView header = (TextView)view.findViewById(R.id.header);
134                header.setText(c.mCityName);
135            } else { // City view
136                // Make sure to recycle a City view only
137                if (view == null || view.findViewById(R.id.city_name) == null) {
138                    view = mInflater.inflate(R.layout.city_list_item, parent, false);
139                }
140                view.setOnClickListener(CitiesActivity.this);
141                TextView name = (TextView)view.findViewById(R.id.city_name);
142                TextView tz = (TextView)view.findViewById(R.id.city_time);
143                CheckBox cb = (CheckBox)view.findViewById(R.id.city_onoff);
144                cb.setTag(c);
145                cb.setChecked(mSelectedCitiesList.containsKey(c.mCityId));
146                cb.setOnCheckedChangeListener(CitiesActivity.this);
147                mCalendar.setTimeZone(TimeZone.getTimeZone(c.mTimeZone));
148                tz.setText(DateFormat.format(mIs24HoursMode ? "k:mm" : "h:mmaa", mCalendar));
149                name.setText(c.mCityName);
150            }
151            return view;
152        }
153
154        public void set24HoursMode(Context c) {
155            mIs24HoursMode = Alarms.get24HourMode(c);
156            notifyDataSetChanged();
157        }
158
159        private void loadCitiesDataBase(Context c) {
160            Resources r = c.getResources();
161            // Read strings array of name,timezone, id
162            // make sure the list are the same length
163            String [] cities = r.getStringArray(R.array.cities_names);
164            String [] timezones = r.getStringArray(R.array.cities_tz);
165            String [] ids = r.getStringArray(R.array.cities_id);
166            if (cities.length != timezones.length || ids.length != cities.length) {
167                Log.wtf("City lists sizes are not the same, cannot use the data");
168                return;
169             }
170             CityObj[] tempList = new CityObj [cities.length];
171             for (int i = 0; i < cities.length; i++) {
172                tempList[i] = new CityObj(cities[i], timezones[i], ids[i]);
173             }
174             // Sort alphabetically
175            Arrays.sort(tempList, new Comparator<CityObj> () {
176                @Override
177                public int compare(CityObj c1, CityObj c2) {
178                    return mCollator.compare(c1.mCityName, c2.mCityName);
179                }
180            });
181            //Create section indexer and add headers to the cities list
182            String val = null;
183            ArrayList<String> sections = new ArrayList<String> ();
184            ArrayList<Integer> positions = new ArrayList<Integer> ();
185            ArrayList<CityObj> items = new ArrayList<CityObj>();
186            int count = 0;
187            for (int i = 0; i < tempList.length; i++) {
188                CityObj city = tempList[i];
189                if (city.mCityId.equals(DELETED_ENTRY)) {
190                    continue;
191                }
192                if (!city.mCityName.substring(0, 1).equals(val)) {
193                    val = city.mCityName.substring(0, 1);
194                    sections.add((new String(val)).toUpperCase());
195                    positions.add(count);
196                    // Add a header
197                    items.add(new CityObj(val, null, null));
198                    count++;
199                }
200                items.add(city);
201                count++;
202            }
203            mSectionHeaders = sections.toArray();
204            mSectionPositions = positions.toArray();
205            mAllTheCitiesList = items.toArray();
206         }
207
208        @Override
209        public int getPositionForSection(int section) {
210            return (Integer) mSectionPositions[section];
211        }
212
213        @Override
214        public int getSectionForPosition(int p) {
215            for (int i = 0; i < mSectionPositions.length - 1; i++) {
216                if (p >= (Integer)mSectionPositions[i] && p < (Integer)mSectionPositions[i + 1]) {
217                    return i;
218                }
219            }
220            if (p >= (Integer)mSectionPositions[mSectionPositions.length - 1]) {
221                return mSectionPositions.length - 1;
222            }
223            return 0;
224        }
225
226        @Override
227        public Object[] getSections() {
228            return mSectionHeaders;
229        }
230    }
231
232
233    @Override
234    protected void onCreate(Bundle icicle) {
235        super.onCreate(icicle);
236        mFactory = LayoutInflater.from(this);
237        updateLayout();
238    }
239
240    private void updateLayout() {
241        setContentView(R.layout.cities_activity);
242        mCitiesList = (ListView) findViewById(R.id.cities_list);
243        mCitiesList.setFastScrollAlwaysVisible(true);
244        mCitiesList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
245        mCitiesList.setFastScrollEnabled(true);
246        mUserSelectedCities = Cities.readCitiesFromSharedPrefs(
247                PreferenceManager.getDefaultSharedPreferences(this));
248        mAdapter = new CityAdapter(this, mUserSelectedCities, mFactory);
249        mCitiesList.setAdapter(mAdapter);
250        ActionBar actionBar = getActionBar();
251        if (actionBar != null) {
252            actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
253        }
254    }
255
256    @Override
257    public void onResume() {
258        super.onResume();
259        if (mAdapter != null) {
260            mAdapter.set24HoursMode(this);
261        }
262    }
263
264
265    @Override
266    public void onPause() {
267        super.onPause();
268        Cities.saveCitiesToSharedPrefs(PreferenceManager.getDefaultSharedPreferences(this),
269                mUserSelectedCities);
270        Intent i = new Intent(Cities.WORLDCLOCK_UPDATE_INTENT);
271        sendBroadcast(i);
272    }
273
274    @Override
275    public boolean onOptionsItemSelected(MenuItem item) {
276        switch (item.getItemId()) {
277            case R.id.menu_item_settings:
278                startActivity(new Intent(this, SettingsActivity.class));
279                return true;
280            case R.id.menu_item_help:
281                Intent i = item.getIntent();
282                if (i != null) {
283                    try {
284                        startActivity(i);
285                    } catch (ActivityNotFoundException e) {
286                        // No activity found to match the intent - ignore
287                    }
288                }
289                return true;
290            case android.R.id.home:
291                Intent intent = new Intent(this, DeskClock.class);
292                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
293                startActivity(intent);
294                return true;
295            default:
296                break;
297        }
298        return super.onOptionsItemSelected(item);
299    }
300
301    @Override
302    public boolean onCreateOptionsMenu(Menu menu) {
303        getMenuInflater().inflate(R.menu.cities_menu, menu);
304        MenuItem help = menu.findItem(R.id.menu_item_help);
305        if (help != null) {
306            Utils.prepareHelpMenuItem(this, help);
307        }
308        return super.onCreateOptionsMenu(menu);
309    }
310
311    @Override
312    public void onCheckedChanged(CompoundButton b, boolean checked) {
313        CityObj c = (CityObj)b.getTag();
314        if (checked) {
315            mUserSelectedCities.put(c.mCityId, c);
316        } else {
317            mUserSelectedCities.remove(c.mCityId);
318        }
319    }
320
321    @Override
322    public void onClick(View v) {
323        CompoundButton b = (CompoundButton)v.findViewById(R.id.city_onoff);
324        boolean checked = b.isChecked();
325        onCheckedChanged(b, checked);
326        b.setChecked(!checked);
327    }
328}
329