1/*
2 * Copyright (C) 2006 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.settings;
18
19import android.app.Activity;
20import android.app.AlarmManager;
21import android.app.ListFragment;
22import android.content.Context;
23import android.os.Bundle;
24import android.support.annotation.NonNull;
25import android.view.LayoutInflater;
26import android.view.Menu;
27import android.view.MenuInflater;
28import android.view.MenuItem;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.ListView;
32import android.widget.SimpleAdapter;
33
34import com.android.settingslib.datetime.ZoneGetter;
35
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41import java.util.TimeZone;
42
43/**
44 * The class displaying a list of time zones that match a filter string
45 * such as "Africa", "Europe", etc. Choosing an item from the list will set
46 * the time zone. Pressing Back without choosing from the list will not
47 * result in a change in the time zone setting.
48 */
49public class ZonePicker extends ListFragment {
50    private static final String TAG = "ZonePicker";
51
52    public interface ZoneSelectionListener {
53        // You can add any argument if you really need it...
54        void onZoneSelected(TimeZone tz);
55    }
56
57    private static final int MENU_TIMEZONE = Menu.FIRST+1;
58    private static final int MENU_ALPHABETICAL = Menu.FIRST;
59
60    private boolean mSortedByTimezone;
61
62    private SimpleAdapter mTimezoneSortedAdapter;
63    private SimpleAdapter mAlphabeticalAdapter;
64
65    private ZoneSelectionListener mListener;
66
67    /**
68     * Constructs an adapter with TimeZone list. Sorted by TimeZone in default.
69     *
70     * @param sortedByName use Name for sorting the list.
71     */
72    public static SimpleAdapter constructTimezoneAdapter(Context context,
73            boolean sortedByName) {
74        return constructTimezoneAdapter(context, sortedByName,
75                R.layout.date_time_setup_custom_list_item_2);
76    }
77
78    /**
79     * Constructs an adapter with TimeZone list. Sorted by TimeZone in default.
80     *
81     * @param sortedByName use Name for sorting the list.
82     */
83    public static SimpleAdapter constructTimezoneAdapter(Context context,
84            boolean sortedByName, int layoutId) {
85        final String[] from = new String[] {ZoneGetter.KEY_DISPLAYNAME, ZoneGetter.KEY_GMT};
86        final int[] to = new int[] {android.R.id.text1, android.R.id.text2};
87
88        final String sortKey = (sortedByName ? ZoneGetter.KEY_DISPLAYNAME : ZoneGetter.KEY_OFFSET);
89        final MyComparator comparator = new MyComparator(sortKey);
90        final List<Map<String, Object>> sortedList = ZoneGetter.getZonesList(context);
91        Collections.sort(sortedList, comparator);
92        final SimpleAdapter adapter = new SimpleAdapter(context,
93                sortedList,
94                layoutId,
95                from,
96                to);
97
98        return adapter;
99    }
100
101    /**
102     * Searches {@link TimeZone} from the given {@link SimpleAdapter} object, and returns
103     * the index for the TimeZone.
104     *
105     * @param adapter SimpleAdapter constructed by
106     * {@link #constructTimezoneAdapter(Context, boolean)}.
107     * @param tz TimeZone to be searched.
108     * @return Index for the given TimeZone. -1 when there's no corresponding list item.
109     * returned.
110     */
111    public static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) {
112        final String defaultId = tz.getID();
113        final int listSize = adapter.getCount();
114        for (int i = 0; i < listSize; i++) {
115            // Using HashMap<String, Object> induces unnecessary warning.
116            final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i);
117            final String id = (String)map.get(ZoneGetter.KEY_ID);
118            if (defaultId.equals(id)) {
119                // If current timezone is in this list, move focus to it
120                return i;
121            }
122        }
123        return -1;
124    }
125
126    /**
127     * @param item one of items in adapters. The adapter should be constructed by
128     * {@link #constructTimezoneAdapter(Context, boolean)}.
129     * @return TimeZone object corresponding to the item.
130     */
131    public static TimeZone obtainTimeZoneFromItem(Object item) {
132        return TimeZone.getTimeZone((String)((Map<?, ?>)item).get(ZoneGetter.KEY_ID));
133    }
134
135    @Override
136    public void onActivityCreated(Bundle savedInstanceState) {
137        super.onActivityCreated(savedInstanceState);
138
139        final Activity activity = getActivity();
140        mTimezoneSortedAdapter = constructTimezoneAdapter(activity, false);
141        mAlphabeticalAdapter = constructTimezoneAdapter(activity, true);
142
143        // Sets the adapter
144        setSorting(true);
145        setHasOptionsMenu(true);
146    }
147
148    @Override
149    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
150            Bundle savedInstanceState) {
151        final View view = super.onCreateView(inflater, container, savedInstanceState);
152        final ListView list = (ListView) view.findViewById(android.R.id.list);
153        Utils.forcePrepareCustomPreferencesList(container, view, list, false);
154        return view;
155    }
156
157    @Override
158    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
159        menu.add(0, MENU_ALPHABETICAL, 0, R.string.zone_list_menu_sort_alphabetically)
160            .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
161        menu.add(0, MENU_TIMEZONE, 0, R.string.zone_list_menu_sort_by_timezone)
162            .setIcon(R.drawable.ic_menu_3d_globe);
163        super.onCreateOptionsMenu(menu, inflater);
164    }
165
166    @Override
167    public void onPrepareOptionsMenu(Menu menu) {
168        if (mSortedByTimezone) {
169            menu.findItem(MENU_TIMEZONE).setVisible(false);
170            menu.findItem(MENU_ALPHABETICAL).setVisible(true);
171        } else {
172            menu.findItem(MENU_TIMEZONE).setVisible(true);
173            menu.findItem(MENU_ALPHABETICAL).setVisible(false);
174        }
175    }
176
177    @Override
178    public boolean onOptionsItemSelected(MenuItem item) {
179        switch (item.getItemId()) {
180
181            case MENU_TIMEZONE:
182                setSorting(true);
183                return true;
184
185            case MENU_ALPHABETICAL:
186                setSorting(false);
187                return true;
188
189            default:
190                return false;
191        }
192    }
193
194    public void setZoneSelectionListener(ZoneSelectionListener listener) {
195        mListener = listener;
196    }
197
198    private void setSorting(boolean sortByTimezone) {
199        final SimpleAdapter adapter =
200                sortByTimezone ? mTimezoneSortedAdapter : mAlphabeticalAdapter;
201        setListAdapter(adapter);
202        mSortedByTimezone = sortByTimezone;
203        final int defaultIndex = getTimeZoneIndex(adapter, TimeZone.getDefault());
204        if (defaultIndex >= 0) {
205            setSelection(defaultIndex);
206        }
207    }
208
209    @Override
210    public void onListItemClick(ListView listView, View v, int position, long id) {
211        // Ignore extra clicks
212        if (!isResumed()) return;
213        final Map<?, ?> map = (Map<?, ?>)listView.getItemAtPosition(position);
214        final String tzId = (String) map.get(ZoneGetter.KEY_ID);
215
216        // Update the system timezone value
217        final Activity activity = getActivity();
218        final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
219        alarm.setTimeZone(tzId);
220        final TimeZone tz = TimeZone.getTimeZone(tzId);
221        if (mListener != null) {
222            mListener.onZoneSelected(tz);
223        } else {
224            getActivity().onBackPressed();
225        }
226    }
227
228    private static class MyComparator implements Comparator<Map<?, ?>> {
229        private String mSortingKey;
230
231        public MyComparator(String sortingKey) {
232            mSortingKey = sortingKey;
233        }
234
235        public void setSortingKey(String sortingKey) {
236            mSortingKey = sortingKey;
237        }
238
239        public int compare(Map<?, ?> map1, Map<?, ?> map2) {
240            Object value1 = map1.get(mSortingKey);
241            Object value2 = map2.get(mSortingKey);
242
243            /*
244             * This should never happen, but just in-case, put non-comparable
245             * items at the end.
246             */
247            if (!isComparable(value1)) {
248                return isComparable(value2) ? 1 : 0;
249            } else if (!isComparable(value2)) {
250                return -1;
251            }
252
253            return ((Comparable) value1).compareTo(value2);
254        }
255
256        private boolean isComparable(Object value) {
257            return (value != null) && (value instanceof Comparable);
258        }
259    }
260}
261