Merge "Incorrect top lap time format once any lap >=10m" into ics-ub-clock-amazon
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bd35504..1edc2e1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -78,7 +78,7 @@
 
         <activity android:name=".worldclock.CitiesActivity"
                 android:label="@string/cities_activity_title"
-                android:theme="@style/SettingsTheme"
+                android:theme="@style/CitiesTheme"
                 android:taskAffinity=""
                 android:excludeFromRecents="true"
                 >
diff --git a/res/drawable-xxhdpi/btn_check_off_holo_dark.png b/res/drawable-xxhdpi/btn_check_off_holo_dark.png
new file mode 100644
index 0000000..1330a64
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_check_off_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/btn_check_on_holo_dark.png b/res/drawable-xxhdpi/btn_check_on_holo_dark.png
new file mode 100644
index 0000000..71a4672
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_check_on_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/fastscroll_thumb_default_holo.png b/res/drawable-xxhdpi/fastscroll_thumb_default_holo.png
new file mode 100644
index 0000000..675be51
--- /dev/null
+++ b/res/drawable-xxhdpi/fastscroll_thumb_default_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/fastscroll_thumb_pressed_holo.png b/res/drawable-xxhdpi/fastscroll_thumb_pressed_holo.png
new file mode 100644
index 0000000..dbcf1d5
--- /dev/null
+++ b/res/drawable-xxhdpi/fastscroll_thumb_pressed_holo.png
Binary files differ
diff --git a/res/drawable/red_checkbox.xml b/res/drawable/red_checkbox.xml
new file mode 100644
index 0000000..80b36e9
--- /dev/null
+++ b/res/drawable/red_checkbox.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_checked="true"
+        android:drawable="@drawable/btn_check_on_holo_dark" />
+
+    <item android:state_checked="false"
+        android:drawable="@drawable/btn_check_off_holo_dark" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/red_fastscroll_thumb.xml b/res/drawable/red_fastscroll_thumb.xml
new file mode 100644
index 0000000..008d215
--- /dev/null
+++ b/res/drawable/red_fastscroll_thumb.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/fastscroll_thumb_pressed_holo" />
+
+    <item android:state_pressed="false"
+        android:drawable="@drawable/fastscroll_thumb_default_holo" />
+</selector>
\ No newline at end of file
diff --git a/res/layout/city_list_item.xml b/res/layout/city_list_item.xml
index 3cd0c12..0ed49de 100644
--- a/res/layout/city_list_item.xml
+++ b/res/layout/city_list_item.xml
@@ -25,6 +25,7 @@
     <CheckBox
         android:id="@+id/city_onoff"
         android:clickable="false"
+        android:button="@drawable/red_checkbox"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content" />
     <TextView
diff --git a/res/menu/cities_menu.xml b/res/menu/cities_menu.xml
index 305ad6b..552a41b 100644
--- a/res/menu/cities_menu.xml
+++ b/res/menu/cities_menu.xml
@@ -22,6 +22,9 @@
         android:actionViewClass="android.widget.SearchView"
         android:imeOptions="actionSearch"
         android:orderInCategory="1" />
+    <item android:id="@+id/menu_item_sort"
+        android:title="@string/menu_item_sort_by_gmt_offset"
+        android:showAsAction="never" />
     <item android:id="@+id/menu_item_settings"
         android:title="@string/menu_item_settings"
         android:icon="@android:drawable/ic_menu_preferences"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ac11ec9..3f3e41e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -77,7 +77,7 @@
     <dimen name="dialpad_font_size">48sp</dimen>
     <dimen name="dialpad_font_size_ampm">24sp</dimen>
     <dimen name="city_name_font_size">24sp</dimen>
-    <dimen name="city_time_font_size">14sp</dimen>
+    <dimen name="city_time_font_size">18sp</dimen>
 
     <dimen name="tablet_dialpad_font_size">60sp</dimen>
     <dimen name="tablet_dialpad_font_size_ampm">30sp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a068bbc..785d240 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -329,6 +329,13 @@
     <string name="menu_item_help">Help</string>
     <!-- Menu item on clock screen to enter night mode. -->
     <string name="menu_item_night_mode">Night mode</string>
+    <!-- Menu item on Cities screen to sort by GMT offset -->
+    <string name="menu_item_sort_by_gmt_offset">Sort by time</string>
+    <!-- Menu item on Cities screen to sort by alphabetical order -->
+    <string name="menu_item_sort_by_name">Sort by name</string>
+
+    <!-- Label for selected cities in Cities list view -->
+    <string name="selected_cities_label">Selected Cities</string>
 
     <!-- Stop Watch strings -->
     <!-- Describes the purpose of the button to resume running a stopwatch -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2b133d7..61bcca2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -68,6 +68,10 @@
         <item name="android:selectableItemBackground">@drawable/item_background</item>
     </style>
 
+    <style name="CitiesTheme" parent="SettingsTheme">
+        <item name="android:fastScrollThumbDrawable">@drawable/red_fastscroll_thumb</item>
+    </style>
+
     <style name="SettingsTheme.ActionBar" parent="@android:style/Widget.Holo.ActionBar">
         <item name="android:titleTextStyle">@style/SettingsTheme.TextAppearance.ActionBar.Title</item>
         <item name="android:displayOptions">useLogo|showTitle</item>
diff --git a/src/com/android/deskclock/Alarm.java b/src/com/android/deskclock/Alarm.java
index 2d121bc..3ce7474 100644
--- a/src/com/android/deskclock/Alarm.java
+++ b/src/com/android/deskclock/Alarm.java
@@ -26,7 +26,6 @@
 
 import java.text.DateFormatSymbols;
 import java.util.Calendar;
-import java.util.HashMap;
 import java.util.HashSet;
 
 public final class Alarm implements Parcelable {
@@ -53,7 +52,7 @@
         p.writeInt(enabled ? 1 : 0);
         p.writeInt(hour);
         p.writeInt(minutes);
-        p.writeInt(daysOfWeek.getCoded());
+        p.writeInt(daysOfWeek.getBitSet());
         // We don't need the alarmTime field anymore, but write 0 to be backwards compatible
         p.writeLong(0);
         p.writeInt(vibrate ? 1 : 0);
@@ -227,18 +226,17 @@
 
     // Creates a default alarm at the current time.
     public Alarm() {
-        id = -1;
-        hour = 0;
-        minutes = 0;
-        vibrate = true;
-        daysOfWeek = new DaysOfWeek(0);
-        label = "";
-        alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+        this(0, 0);
     }
 
-    public long calculateAlarmTime() {
-        // Would it be safe to cache this value in memory?
-        return Alarms.calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
+    public Alarm(int hour, int minutes) {
+        this.id = -1;
+        this.hour = hour;
+        this.minutes = minutes;
+        this.vibrate = true;
+        this.daysOfWeek = new DaysOfWeek(0);
+        this.label = "";
+        this.alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
     }
 
     public String getLabelOrDefault(Context context) {
@@ -261,6 +259,30 @@
     }
 
 
+    public long calculateAlarmTime() {
+        // start with now
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+
+        int nowHour = c.get(Calendar.HOUR_OF_DAY);
+        int nowMinute = c.get(Calendar.MINUTE);
+
+        // if alarm is behind current time, advance one day
+        if ((hour < nowHour  || (hour == nowHour && minutes <= nowMinute))) {
+            c.add(Calendar.DAY_OF_YEAR, 1);
+        }
+        c.set(Calendar.HOUR_OF_DAY, hour);
+        c.set(Calendar.MINUTE, minutes);
+        c.set(Calendar.SECOND, 0);
+        c.set(Calendar.MILLISECOND, 0);
+
+        int addDays = daysOfWeek.calculateDaysToNextAlarm(c);
+        if (addDays > 0) {
+            c.add(Calendar.DAY_OF_WEEK, addDays);
+        }
+        return c.getTimeInMillis();
+    }
+
     /*
      * Days of week code as a single int.
      * 0x00: no day
@@ -272,31 +294,37 @@
      * 0x20: Saturday
      * 0x40: Sunday
      */
-    static final class DaysOfWeek {
+    public static final class DaysOfWeek {
+        // Number if days in the week.
+        public static final int DAYS_IN_A_WEEK = 7;
 
-        private static int[] DAY_MAP = new int[] {
-            Calendar.MONDAY,
-            Calendar.TUESDAY,
-            Calendar.WEDNESDAY,
-            Calendar.THURSDAY,
-            Calendar.FRIDAY,
-            Calendar.SATURDAY,
-            Calendar.SUNDAY,
-        };
+        // Value when all days are set
+        public static final int ALL_DAYS_SET = 0x7f;
 
+        // Value when no days are set
+        public static final int NO_DAYS_SET = 0;
 
-        private static HashMap<Integer, Integer> DAY_TO_BIT_MASK = new HashMap<Integer, Integer>();
-        static {
-            for (int i = 0; i < DAY_MAP.length; i++) {
-                DAY_TO_BIT_MASK.put(DAY_MAP[i], i);
-            }
+        /**
+         * Need to have monday start at index 0 to be backwards compatible. This converts
+         * Calendar.DAY_OF_WEEK constants to our internal bit structure.
+         */
+        private static int convertDayToBitIndex(int day) {
+            return (day + 5) % DAYS_IN_A_WEEK;
+        }
+
+        /**
+         * Need to have monday start at index 0 to be backwards compatible. This converts
+         * our bit structure to Calendar.DAY_OF_WEEK constant value.
+         */
+        private static int convertBitIndexToDay(int bitIndex) {
+            return (bitIndex + 1) % DAYS_IN_A_WEEK + 1;
         }
 
         // Bitmask of all repeating days
-        private int mDays;
+        private int mBitSet;
 
-        DaysOfWeek(int days) {
-            mDays = days;
+        public DaysOfWeek(int bitSet) {
+            mBitSet = bitSet;
         }
 
         public String toString(Context context, boolean showNever) {
@@ -311,121 +339,114 @@
             StringBuilder ret = new StringBuilder();
 
             // no days
-            if (mDays == 0) {
-                return showNever ?
-                        context.getText(R.string.never).toString() : "";
+            if (mBitSet == NO_DAYS_SET) {
+                return showNever ? context.getText(R.string.never).toString() : "";
             }
 
             // every day
-            if (mDays == 0x7f) {
+            if (mBitSet == ALL_DAYS_SET) {
                 return context.getText(R.string.every_day).toString();
             }
 
             // count selected days
-            int dayCount = 0, days = mDays;
-            while (days > 0) {
-                if ((days & 1) == 1) dayCount++;
-                days >>= 1;
+            int dayCount = 0;
+            int bitSet = mBitSet;
+            while (bitSet > 0) {
+                if ((bitSet & 1) == 1) dayCount++;
+                bitSet >>= 1;
             }
 
             // short or long form?
             DateFormatSymbols dfs = new DateFormatSymbols();
             String[] dayList = (forAccessibility || dayCount <= 1) ?
-                            dfs.getWeekdays() :
-                            dfs.getShortWeekdays();
+                    dfs.getWeekdays() :
+                    dfs.getShortWeekdays();
 
             // selected days
-            for (int i = 0; i < 7; i++) {
-                if ((mDays & (1 << i)) != 0) {
-                    ret.append(dayList[DAY_MAP[i]]);
+            for (int bitIndex = 0; bitIndex < DAYS_IN_A_WEEK; bitIndex++) {
+                if ((mBitSet & (1 << bitIndex)) != 0) {
+                    ret.append(dayList[convertBitIndexToDay(bitIndex)]);
                     dayCount -= 1;
-                    if (dayCount > 0) ret.append(
-                            context.getText(R.string.day_concat));
+                    if (dayCount > 0) ret.append(context.getText(R.string.day_concat));
                 }
             }
             return ret.toString();
         }
 
-        private boolean isSet(int day) {
-            return ((mDays & (1 << day)) > 0);
-        }
-
         /**
-         * Sets the repeat day for the alarm.
+         * Enables or disable certain days of the week.
          *
-         * @param dayOfWeek One of: Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, etc.
-         * @param set Whether to set or unset.
+         * @param daysOfWeek Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, etc.
          */
-        public void setDayOfWeek(int dayOfWeek, boolean set) {
-            final int bitIndex = DAY_TO_BIT_MASK.get(dayOfWeek);
-            set(bitIndex, set);
-        }
-
-        public void set(int day, boolean set) {
-            if (set) {
-                mDays |= (1 << day);
-            } else {
-                mDays &= ~(1 << day);
+        public void setDaysOfWeek(boolean value, int ... daysOfWeek) {
+            for (int day : daysOfWeek) {
+                setBit(convertDayToBitIndex(day), value);
             }
         }
 
-        public void set(DaysOfWeek dow) {
-            mDays = dow.mDays;
+        private boolean isBitEnabled(int bitIndex) {
+            return ((mBitSet & (1 << bitIndex)) > 0);
         }
 
-        public int getCoded() {
-            return mDays;
+        private void setBit(int bitIndex, boolean set) {
+            if (set) {
+                mBitSet |= (1 << bitIndex);
+            } else {
+                mBitSet &= ~(1 << bitIndex);
+            }
+        }
+
+        public void setBitSet(int bitSet) {
+            mBitSet = bitSet;
+        }
+
+        public int getBitSet() {
+            return mBitSet;
         }
 
         public HashSet<Integer> getSetDays() {
             final HashSet<Integer> set = new HashSet<Integer>();
-            for (int i = 0; i < 7; i++) {
-                if (isSet(i)) {
-                    set.add(DAY_MAP[i]);
+            for (int bitIndex = 0; bitIndex < DAYS_IN_A_WEEK; bitIndex++) {
+                if (isBitEnabled(bitIndex)) {
+                    set.add(convertBitIndexToDay(bitIndex));
                 }
             }
             return set;
         }
 
-        // Returns days of week encoded in an array of booleans.
-        public boolean[] getBooleanArray() {
-            boolean[] ret = new boolean[7];
-            for (int i = 0; i < 7; i++) {
-                ret[i] = isSet(i);
-            }
-            return ret;
-        }
-
-        public boolean isRepeatSet() {
-            return mDays != 0;
+        public boolean isRepeating() {
+            return mBitSet != NO_DAYS_SET;
         }
 
         /**
-         * returns number of days from today until next alarm
-         * @param c must be set to today
+         * Returns number of days from today until next alarm.
+         *
+         * @param current must be set to today
          */
-        public int getNextAlarm(Calendar c) {
-            if (mDays == 0) {
+        public int calculateDaysToNextAlarm(Calendar current) {
+            if (!isRepeating()) {
                 return -1;
             }
 
-            int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;
-
-            int day = 0;
             int dayCount = 0;
-            for (; dayCount < 7; dayCount++) {
-                day = (today + dayCount) % 7;
-                if (isSet(day)) {
+            int currentDayBit = convertDayToBitIndex(current.get(Calendar.DAY_OF_WEEK));
+            for (; dayCount < DAYS_IN_A_WEEK; dayCount++) {
+                int nextAlarmBit = (currentDayBit + dayCount) % DAYS_IN_A_WEEK;
+                if (isBitEnabled(nextAlarmBit)) {
                     break;
                 }
             }
             return dayCount;
         }
 
+        public void clearAllDays() {
+            mBitSet = 0;
+        }
+
         @Override
         public String toString() {
             return "DaysOfWeek{" +
-                    "mDays=" + mDays +
+                    "mBitSet=" + mBitSet +
                     '}';
         }
     }
diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java
index f7533c8..84b3bd0 100644
--- a/src/com/android/deskclock/AlarmClock.java
+++ b/src/com/android/deskclock/AlarmClock.java
@@ -767,7 +767,7 @@
             });
             itemHolder.clickableLabel.setOnLongClickListener(mLongClickListener);
 
-            if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeatSet()) {
+            if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeating()) {
                 itemHolder.repeat.setChecked(true);
                 itemHolder.repeatDays.setVisibility(View.VISIBLE);
                 itemHolder.repeatDays.setOnLongClickListener(mLongClickListener);
@@ -791,13 +791,10 @@
                         // Set all previously set days
                         // or
                         // Set all days if no previous.
-                        final int daysOfWeekCoded = mPreviousDaysOfWeekMap.getInt("" + alarm.id);
-                        if (daysOfWeekCoded == 0) {
-                            for (int day : DAY_ORDER) {
-                                alarm.daysOfWeek.setDayOfWeek(day, true);
-                            }
-                        } else {
-                            alarm.daysOfWeek.set(new Alarm.DaysOfWeek(daysOfWeekCoded));
+                        final int bitSet = mPreviousDaysOfWeekMap.getInt("" + alarm.id);
+                        alarm.daysOfWeek.setBitSet(bitSet);
+                        if (!alarm.daysOfWeek.isRepeating()) {
+                            alarm.daysOfWeek.setDaysOfWeek(true, DAY_ORDER);
                         }
                         updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek);
                     } else {
@@ -805,11 +802,11 @@
                         mRepeatChecked.remove(alarm.id);
 
                         // Remember the set days in case the user wants it back.
-                        final int daysOfWeekCoded = alarm.daysOfWeek.getCoded();
-                        mPreviousDaysOfWeekMap.putInt("" + alarm.id, daysOfWeekCoded);
+                        final int bitSet = alarm.daysOfWeek.getBitSet();
+                        mPreviousDaysOfWeekMap.putInt("" + alarm.id, bitSet);
 
                         // Remove all repeat days
-                        alarm.daysOfWeek.set(new Alarm.DaysOfWeek(0));
+                        alarm.daysOfWeek.clearAllDays();
                     }
                     asyncUpdateAlarm(alarm, false);
                 }
@@ -830,20 +827,22 @@
                         itemHolder.dayButtons[buttonIndex].toggle();
                         final boolean checked = itemHolder.dayButtons[buttonIndex].isChecked();
                         int day = DAY_ORDER[buttonIndex];
-                        alarm.daysOfWeek.setDayOfWeek(day, checked);
+                        alarm.daysOfWeek.setDaysOfWeek(checked, day);
                         if (checked) {
                             turnOnDayOfWeek(itemHolder, buttonIndex);
                         } else {
                             turnOffDayOfWeek(itemHolder, buttonIndex);
 
                             // See if this was the last day, if so, un-check the repeat box.
-                            if (alarm.daysOfWeek.getCoded() == 0) {
+                            if (!alarm.daysOfWeek.isRepeating()) {
                                 itemHolder.repeatDays.setVisibility(View.GONE);
                                 itemHolder.repeat.setTextColor(mColorDim);
                                 mRepeatChecked.remove(alarm.id);
 
-                                // Remember the set days in case the user wants it back.
-                                mPreviousDaysOfWeekMap.putInt("" + alarm.id, 0);
+                                // Set history to no days, so it will be everyday when repeat is
+                                // turned back on
+                                mPreviousDaysOfWeekMap.putInt("" + alarm.id,
+                                        Alarm.DaysOfWeek.NO_DAYS_SET);
                             }
                         }
                         asyncUpdateAlarm(alarm, false);
@@ -1221,7 +1220,7 @@
     }
 
     private void popToast(Alarm alarm) {
-        AlarmUtils.popAlarmSetToast(this, alarm.hour, alarm.minutes, alarm.daysOfWeek);
+        AlarmUtils.popAlarmSetToast(this, alarm);
     }
 
     /***
diff --git a/src/com/android/deskclock/AlarmReceiver.java b/src/com/android/deskclock/AlarmReceiver.java
index d1bef5d..03c7a93 100644
--- a/src/com/android/deskclock/AlarmReceiver.java
+++ b/src/com/android/deskclock/AlarmReceiver.java
@@ -105,7 +105,7 @@
         // Disable the snooze alert if this alarm is the snooze.
         Alarms.disableSnoozeAlert(context, alarm.id);
         // Disable this alarm if it does not repeat.
-        if (!alarm.daysOfWeek.isRepeatSet()) {
+        if (!alarm.daysOfWeek.isRepeating()) {
             Alarms.enableAlarm(context, alarm.id, false);
         } else {
             // Enable the next alert if there is one. The above call to
diff --git a/src/com/android/deskclock/AlarmUtils.java b/src/com/android/deskclock/AlarmUtils.java
index 1df3874..a801eef 100644
--- a/src/com/android/deskclock/AlarmUtils.java
+++ b/src/com/android/deskclock/AlarmUtils.java
@@ -84,10 +84,7 @@
      * Display a toast that tells the user how long until the alarm
      * goes off.  This helps prevent "am/pm" mistakes.
      */
-    public static void popAlarmSetToast(Context context, int hour, int minute,
-                                 Alarm.DaysOfWeek daysOfWeek) {
-        popAlarmSetToast(context,
-                Alarms.calculateAlarm(hour, minute, daysOfWeek)
-                        .getTimeInMillis());
+    public static void popAlarmSetToast(Context context, Alarm alarm) {
+        popAlarmSetToast(context, alarm.calculateAlarmTime());
     }
 }
diff --git a/src/com/android/deskclock/Alarms.java b/src/com/android/deskclock/Alarms.java
index 7bb1ec9..f666cc1 100644
--- a/src/com/android/deskclock/Alarms.java
+++ b/src/com/android/deskclock/Alarms.java
@@ -166,7 +166,7 @@
                 null, null);
     }
 
-    private static ContentValues createContentValues(Alarm alarm) {
+    public static ContentValues createContentValues(Alarm alarm) {
         ContentValues values = new ContentValues(8);
         // -1 means generate new id.
         if (alarm.id != -1) {
@@ -178,7 +178,7 @@
         values.put(Alarm.Columns.MINUTES, alarm.minutes);
         // We don't need this field anymore
         values.put(Alarm.Columns.ALARM_TIME, 0);
-        values.put(Alarm.Columns.DAYS_OF_WEEK, alarm.daysOfWeek.getCoded());
+        values.put(Alarm.Columns.DAYS_OF_WEEK, alarm.daysOfWeek.getBitSet());
         values.put(Alarm.Columns.VIBRATE, alarm.vibrate);
         values.put(Alarm.Columns.MESSAGE, alarm.label);
 
@@ -333,11 +333,10 @@
             }
         }
 
-        Alarm alarm = null;
-        Long alarmTime = null;
+        Pair<Alarm, Long> alarmResult = null;
         for (Alarm a : alarms) {
             // Update the alarm if it has been snoozed
-            alarmTime = hasAlarmBeenSnoozed(prefs, a.id) ?
+            Long alarmTime = hasAlarmBeenSnoozed(prefs, a.id) ?
                     prefs.getLong(getAlarmPrefSnoozeTimeKey(a.id), -1) :
                     a.calculateAlarmTime();
 
@@ -349,11 +348,11 @@
             }
             if (alarmTime < minTime) {
                 minTime = alarmTime;
-                alarm = a;
+                alarmResult = Pair.create(a, minTime);
             }
         }
 
-        return alarm != null ? Pair.create(alarm, minTime) : null;
+        return alarmResult;
     }
 
     /**
@@ -369,7 +368,7 @@
                 do {
                     Alarm alarm = new Alarm(cur);
                     // Ignore repeatable alarms
-                    if (alarm.daysOfWeek.isRepeatSet()) {
+                    if (alarm.daysOfWeek.isRepeating()) {
                         continue;
                     }
 
@@ -561,40 +560,6 @@
         context.sendBroadcast(alarmChanged);
     }
 
-    /**
-     * Given an alarm in hours and minutes, return a time suitable for
-     * setting in AlarmManager.
-     */
-    static Calendar calculateAlarm(int hour, int minute,
-            Alarm.DaysOfWeek daysOfWeek) {
-
-        // start with now
-        Calendar c = Calendar.getInstance();
-        c.setTimeInMillis(System.currentTimeMillis());
-
-        int nowHour = c.get(Calendar.HOUR_OF_DAY);
-        int nowMinute = c.get(Calendar.MINUTE);
-
-        // if alarm is behind current time, advance one day
-        if ((hour < nowHour  || (hour == nowHour && minute <= nowMinute))) {
-            c.add(Calendar.DAY_OF_YEAR, 1);
-        }
-        c.set(Calendar.HOUR_OF_DAY, hour);
-        c.set(Calendar.MINUTE, minute);
-        c.set(Calendar.SECOND, 0);
-        c.set(Calendar.MILLISECOND, 0);
-
-        int addDays = daysOfWeek.getNextAlarm(c);
-        if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays);
-        return c;
-    }
-
-    static String formatTime(final Context context, int hour, int minute,
-                             Alarm.DaysOfWeek daysOfWeek) {
-        Calendar c = calculateAlarm(hour, minute, daysOfWeek);
-        return formatTime(context, c);
-    }
-
     /* used by AlarmAlert */
     static String formatTime(final Context context, Calendar c) {
         String format = get24HourMode(context) ? M24 : M12;
diff --git a/src/com/android/deskclock/HandleSetAlarm.java b/src/com/android/deskclock/HandleSetAlarm.java
index 69ef89e..0b3ebe9 100644
--- a/src/com/android/deskclock/HandleSetAlarm.java
+++ b/src/com/android/deskclock/HandleSetAlarm.java
@@ -60,18 +60,15 @@
         }
 
         Cursor c = null;
-        long timeInMillis = Alarms.calculateAlarm(hour, minutes,
-                new Alarm.DaysOfWeek(0)).getTimeInMillis();
         try {
             c = getContentResolver().query(
                     Alarm.Columns.CONTENT_URI,
                     Alarm.Columns.ALARM_QUERY_COLUMNS,
                     Alarm.Columns.HOUR + "=" + hour + " AND " +
                     Alarm.Columns.MINUTES + "=" + minutes + " AND " +
-                    Alarm.Columns.DAYS_OF_WEEK + "=0 AND " +
                     Alarm.Columns.MESSAGE + "=?",
                     new String[] { message }, null);
-            if (handleCursorResult(c, timeInMillis, true, skipUi)) {
+            if (handleCursorResult(c, true, skipUi)) {
                 finish();
                 return;
             }
@@ -81,22 +78,17 @@
             c = null;
         }
 
-        ContentValues values = new ContentValues();
-        values.put(Alarm.Columns.HOUR, hour);
-        values.put(Alarm.Columns.MINUTES, minutes);
-        values.put(Alarm.Columns.MESSAGE, message);
-        values.put(Alarm.Columns.ENABLED, 1);
-        values.put(Alarm.Columns.VIBRATE, 1);
-        values.put(Alarm.Columns.DAYS_OF_WEEK, 0);
-        values.put(Alarm.Columns.ALARM_TIME, timeInMillis);
+        Alarm alarm = new Alarm(hour, minutes);
+        alarm.enabled = true;
+        alarm.label = message;
 
         ContentResolver cr = getContentResolver();
-        Uri result = cr.insert(Alarm.Columns.CONTENT_URI, values);
+        Uri result = cr.insert(Alarm.Columns.CONTENT_URI, Alarms.createContentValues(alarm));
         if (result != null) {
             try {
                 c = cr.query(result, Alarm.Columns.ALARM_QUERY_COLUMNS, null,
                         null, null);
-                handleCursorResult(c, timeInMillis, false, skipUi);
+                handleCursorResult(c, false, skipUi);
             } finally {
                 if (c != null) c.close();
             }
@@ -105,15 +97,20 @@
         finish();
     }
 
-    private boolean handleCursorResult(Cursor c, long timeInMillis,
-            boolean enable, boolean skipUi) {
+    private boolean handleCursorResult(Cursor c, boolean enable, boolean skipUi) {
         if (c != null && c.moveToFirst()) {
             Alarm alarm = new Alarm(c);
+            // We are only allowing the creation or enabling of non-repeating alarms, so skip
+            // them if we see it.
+            if (alarm.daysOfWeek.isRepeating()) {
+                return false;
+            }
+
             if (enable) {
                 Alarms.enableAlarm(this, alarm.id, true);
                 alarm.enabled = true;
             }
-            AlarmUtils.popAlarmSetToast(this, timeInMillis);
+            AlarmUtils.popAlarmSetToast(this, alarm.calculateAlarmTime());
             if (skipUi) {
                 Alarms.setAlarm(this, alarm);
             } else {
diff --git a/src/com/android/deskclock/RepeatPreference.java b/src/com/android/deskclock/RepeatPreference.java
deleted file mode 100644
index 1f837e4..0000000
--- a/src/com/android/deskclock/RepeatPreference.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.deskclock;
-
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.preference.ListPreference;
-import android.util.AttributeSet;
-
-import java.text.DateFormatSymbols;
-import java.util.Calendar;
-
-public class RepeatPreference extends ListPreference {
-
-    // Initial value that can be set with the values saved in the database.
-    private Alarm.DaysOfWeek mDaysOfWeek = new Alarm.DaysOfWeek(0);
-    // New value that will be set if a positive result comes back from the
-    // dialog.
-    private Alarm.DaysOfWeek mNewDaysOfWeek = new Alarm.DaysOfWeek(0);
-
-    public RepeatPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        String[] weekdays = new DateFormatSymbols().getWeekdays();
-        String[] values = new String[] {
-            weekdays[Calendar.MONDAY],
-            weekdays[Calendar.TUESDAY],
-            weekdays[Calendar.WEDNESDAY],
-            weekdays[Calendar.THURSDAY],
-            weekdays[Calendar.FRIDAY],
-            weekdays[Calendar.SATURDAY],
-            weekdays[Calendar.SUNDAY],
-        };
-        setEntries(values);
-        setEntryValues(values);
-    }
-
-    @Override
-    protected void onDialogClosed(boolean positiveResult) {
-        if (positiveResult) {
-            mDaysOfWeek.set(mNewDaysOfWeek);
-            setSummary(mDaysOfWeek.toString(getContext(), true));
-            callChangeListener(mDaysOfWeek);
-        } else {
-            mNewDaysOfWeek.set(mDaysOfWeek);
-        }
-    }
-
-    @Override
-    protected void onPrepareDialogBuilder(Builder builder) {
-        CharSequence[] entries = getEntries();
-        CharSequence[] entryValues = getEntryValues();
-
-        builder.setMultiChoiceItems(
-                entries, mDaysOfWeek.getBooleanArray(),
-                new DialogInterface.OnMultiChoiceClickListener() {
-                    public void onClick(DialogInterface dialog, int which,
-                            boolean isChecked) {
-                        mNewDaysOfWeek.set(which, isChecked);
-                    }
-                });
-    }
-
-    public void setDaysOfWeek(Alarm.DaysOfWeek dow) {
-        mDaysOfWeek.set(dow);
-        mNewDaysOfWeek.set(dow);
-        setSummary(dow.toString(getContext(), true));
-    }
-
-    public Alarm.DaysOfWeek getDaysOfWeek() {
-        return mDaysOfWeek;
-    }
-}
diff --git a/src/com/android/deskclock/Utils.java b/src/com/android/deskclock/Utils.java
index 6cc6ba3..0d57c29 100644
--- a/src/com/android/deskclock/Utils.java
+++ b/src/com/android/deskclock/Utils.java
@@ -38,8 +38,11 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
+import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.style.ForegroundColorSpan;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
@@ -50,13 +53,11 @@
 import com.android.deskclock.timer.Timers;
 import com.android.deskclock.worldclock.CityObj;
 
-import java.text.Collator;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
 import java.util.Calendar;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.Locale;
+import java.util.TimeZone;
 
 
 public class Utils {
@@ -437,8 +438,7 @@
         }
     }
 
-    public static CityObj[] loadCitiesDataBase(Context c) {
-        final Collator collator = Collator.getInstance();
+    public static CityObj[] loadCitiesFromXml(Context c) {
         Resources r = c.getResources();
         // Read strings array of name,timezone, id
         // make sure the list are the same length
@@ -453,17 +453,35 @@
         for (int i = 0; i < cities.length; i++) {
             tempList[i] = new CityObj(cities[i], timezones[i], ids[i]);
         }
-        // Sort alphabetically
-        Arrays.sort(tempList, new Comparator<CityObj> () {
-            @Override
-            public int compare(CityObj c1, CityObj c2) {
-                Comparator<CityObj> mCollator;
-                return collator.compare(c1.mCityName, c2.mCityName);
-            }
-        });
         return tempList;
     }
 
+    /**
+     * Returns string denoting the timezone hour offset (e.g. GMT-8:00)
+     */
+    public static String getGMTHourOffset(TimeZone timezone, boolean showMinutes) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("GMT");
+        int gmtOffset = timezone.getRawOffset();
+        if (gmtOffset < 0) {
+            sb.append('-');
+        } else {
+            sb.append('+');
+        }
+        sb.append(Math.abs(gmtOffset) / DateUtils.HOUR_IN_MILLIS); // Hour
+
+        if (showMinutes) {
+            final int min = (Math.abs(gmtOffset) / (int) DateUtils.MINUTE_IN_MILLIS) % 60;
+            sb.append(':');
+            if (min < 10) {
+                sb.append('0');
+            }
+            sb.append(min);
+        }
+
+        return sb.toString();
+    }
+
     public static String getCityName(CityObj city, CityObj dbCity) {
         return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName;
     }
diff --git a/src/com/android/deskclock/worldclock/CitiesActivity.java b/src/com/android/deskclock/worldclock/CitiesActivity.java
index 47118a4..ecf1ad4 100644
--- a/src/com/android/deskclock/worldclock/CitiesActivity.java
+++ b/src/com/android/deskclock/worldclock/CitiesActivity.java
@@ -18,16 +18,14 @@
 
 import android.app.ActionBar;
 import android.app.Activity;
-import android.app.SearchableInfo;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
-import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
-import android.text.style.ForegroundColorSpan;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -54,8 +52,11 @@
 import com.android.deskclock.Utils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.TimeZone;
 
 /**
@@ -66,6 +67,12 @@
 
     private static final String KEY_SEARCH_QUERY = "search_query";
     private static final String KEY_SEARCH_MODE = "search_mode";
+    private static final String KEY_LIST_POSITION = "list_position";
+
+    private static final String PREF_SORT = "sort_preference";
+
+    private static final int SORT_BY_NAME = 0;
+    private static final int SORT_BY_GMT_OFFSET = 1;
 
     /**
      * This must be false for production. If true, turns on logging, test code,
@@ -80,28 +87,42 @@
     private HashMap<String, CityObj> mUserSelectedCities;
     private Calendar mCalendar;
 
-    private MenuItem mSearchMenu;
     private SearchView mSearchView;
     private StringBuffer mQueryTextBuffer = new StringBuffer();
     private boolean mSearchMode;
+    private int mPosition = -1;
+
+    private SharedPreferences mPrefs;
+    private int mSortType;
+
+    private String mSelectedCitiesHeaderString;
 
     /***
      * Adapter for a list of cities with the respected time zone. The Adapter
      * sorts the list alphabetically and create an indexer.
      ***/
-
     private class CityAdapter extends BaseAdapter implements Filterable, SectionIndexer {
-        private static final String DELETED_ENTRY = "C0";
-        private ArrayList<CityObj> mAllTheCitiesList; // full list of the cities
-        private final HashMap<String, CityObj> mSelectedCitiesList;  // selected cities
+        private static final int VIEW_TYPE_CITY = 0;
+        private static final int VIEW_TYPE_HEADER = 1;
 
-        private ArrayList<CityObj> mDisplayedCitiesList;
-        private final int mSpanColor;
+        private static final String FORMAT_24_HOUR = "k:mm";
+        private static final String FORMAT_12_HOUR = "h:mm aa";
+
+        private static final String DELETED_ENTRY = "C0";
+
+        private List<CityObj> mDisplayedCitiesList;
+
+        private CityObj[] mCities;
+        private CityObj[] mSelectedCities;
+
+        private String[] mSectionHeaders;
+        private Integer[] mSectionPositions;
+
+        private CityNameComparator mSortByNameComparator = new CityNameComparator();
+        private CityGmtOffsetComparator mSortByTimeComparator = new CityGmtOffsetComparator();
 
         private final LayoutInflater mInflater;
         private boolean mIs24HoursMode; // AM/PM or 24 hours mode
-        private Object[] mSectionHeaders;
-        private Object[] mSectionPositions;
 
         private Filter mFilter = new Filter() {
 
@@ -110,19 +131,71 @@
                 FilterResults results = new FilterResults();
                 String modifiedQuery = constraint.toString().trim().toUpperCase();
 
+                ArrayList<CityObj> filteredList = new ArrayList<CityObj>();
+                ArrayList<String> sectionHeaders = new ArrayList<String>();
+                ArrayList<Integer> sectionPositions = new ArrayList<Integer>();
+
+                // If the search query is empty, add in the selected cities
                 if (TextUtils.isEmpty(modifiedQuery)) {
-                    results.values = mAllTheCitiesList;
-                    results.count = mAllTheCitiesList.size();
-                    return results;
+                    if (mSelectedCities.length > 0) {
+                        filteredList.add(new CityObj(mSelectedCitiesHeaderString,
+                                mSelectedCitiesHeaderString,
+                                null));
+                    }
+                    for (CityObj city : mSelectedCities) {
+                        filteredList.add(city);
+                    }
                 }
 
-                ArrayList<CityObj> filteredList = new ArrayList<CityObj>();
-                for (CityObj city : mAllTheCitiesList) {
-                     String cityName = city.mCityName.trim().toUpperCase();
-                     if (city.mCityId != null && cityName.startsWith(modifiedQuery)) {
-                         filteredList.add(city);
-                     }
+                String val = null;
+                int offset = -100000; //some value that cannot be a real offset
+                for (CityObj city : mCities) {
+
+                    // If the city is a deleted entry, ignore it.
+                    if (city.mCityId.equals(DELETED_ENTRY)) {
+                        continue;
+                    }
+
+                    // If the search query is empty, add section headers.
+                    if (TextUtils.isEmpty(modifiedQuery)) {
+
+
+                        // If the list is sorted by name, and the city begins with a letter
+                        // different than the previous city's letter, insert a section header.
+                        if (mSortType == SORT_BY_NAME
+                                && !city.mCityName.substring(0, 1).equals(val)) {
+                                val = city.mCityName.substring(0, 1).toUpperCase();
+                                sectionHeaders.add(val);
+                                sectionPositions.add(filteredList.size());
+                                filteredList.add(new CityObj(val, null, null));
+                        }
+
+                        // If the list is sorted by time, and the gmt offset is different than
+                        // the previous city's gmt offset, insert a section header.
+                        if (mSortType == SORT_BY_GMT_OFFSET) {
+                            TimeZone timezone = TimeZone.getTimeZone(city.mTimeZone);
+                            int newOffset = timezone.getRawOffset();
+                            if (offset != newOffset) {
+                                offset = newOffset;
+                                String offsetString = Utils.getGMTHourOffset(timezone, true);
+                                sectionHeaders.add(offsetString);
+                                sectionPositions.add(filteredList.size());
+                                filteredList.add(new CityObj(null, offsetString, null));
+                            }
+                        }
+                    }
+
+                    // If the city name begins with the query, add the city into the list.
+                    // If the query is empty, the city will automatically be added to the list.
+                    String cityName = city.mCityName.trim().toUpperCase();
+                    if (city.mCityId != null && cityName.startsWith(modifiedQuery)) {
+                        filteredList.add(city);
+                    }
                 }
+
+                mSectionHeaders = sectionHeaders.toArray(new String[sectionHeaders.size()]);
+                mSectionPositions = sectionPositions.toArray(new Integer[sectionPositions.size()]);
+
                 results.values = filteredList;
                 results.count = filteredList.size();
                 return results;
@@ -131,22 +204,61 @@
             @Override
             protected void publishResults(CharSequence constraint, FilterResults results) {
                 mDisplayedCitiesList = (ArrayList<CityObj>) results.values;
+                if (mPosition >= 0) {
+                    mCitiesList.setSelectionFromTop(mPosition, 0);
+                    mPosition = -1;
+                }
                 notifyDataSetChanged();
             }
         };
 
         public CityAdapter(
-                Context context, HashMap<String, CityObj> selectedList, LayoutInflater factory) {
+                Context context, LayoutInflater factory) {
             super();
-            loadCitiesDataBase(context);
-            mSelectedCitiesList = selectedList;
+            loadCities(context);
             mInflater = factory;
             mCalendar = Calendar.getInstance();
             mCalendar.setTimeInMillis(System.currentTimeMillis());
-            mSpanColor = context.getResources().getColor(R.color.clock_blue);
+            Collection<CityObj> selectedCities = mUserSelectedCities.values();
+            mSelectedCities = selectedCities.toArray(new CityObj[selectedCities.size()]);
             set24HoursMode(context);
         }
 
+        public void refreshSelectedCities() {
+            Collection<CityObj> selectedCities = mUserSelectedCities.values();
+            mSelectedCities = selectedCities.toArray(new CityObj[selectedCities.size()]);
+            sortCities(mSortType);
+        }
+
+
+        private void loadCities(Context c) {
+            mCities = Utils.loadCitiesFromXml(c);
+            if (mCities == null) {
+                return;
+            }
+            sortCities(mSortType);
+        }
+
+        public void toggleSort() {
+            if (mSortType == SORT_BY_NAME) {
+                sortCities(SORT_BY_GMT_OFFSET);
+            } else {
+                sortCities(SORT_BY_NAME);
+            }
+        }
+
+        private void sortCities(final int sortType) {
+            mSortType = sortType;
+            Arrays.sort(mCities, sortType == SORT_BY_NAME ? mSortByNameComparator
+                    : mSortByTimeComparator);
+            if (mSelectedCities != null) {
+                Arrays.sort(mSelectedCities, sortType == SORT_BY_NAME ? mSortByNameComparator
+                        : mSortByTimeComparator);
+            }
+            mPrefs.edit().putInt(PREF_SORT, sortType).commit();
+            mFilter.filter(mQueryTextBuffer.toString());
+        }
+
         @Override
         public int getCount() {
             return (mDisplayedCitiesList != null) ? mDisplayedCitiesList.size() : 0;
@@ -179,84 +291,75 @@
             CityObj c = mDisplayedCitiesList.get(position);
             // Header view: A CityObj with nothing but the first letter as the name
             if (c.mCityId == null) {
-                if (view == null || view.findViewById(R.id.header) == null) {
+                if (view == null) {
                     view = mInflater.inflate(R.layout.city_list_header, parent, false);
+                    view.setTag(view.findViewById(R.id.header));
                 }
-                TextView header = (TextView) view.findViewById(R.id.header);
-                header.setText(c.mCityName);
+                ((TextView) view.getTag()).setText(
+                        mSortType == SORT_BY_NAME ? c.mCityName : c.mTimeZone);
             } else { // City view
                 // Make sure to recycle a City view only
-                if (view == null || view.findViewById(R.id.city_name) == null) {
+                if (view == null) {
                     view = mInflater.inflate(R.layout.city_list_item, parent, false);
+                    CityViewHolder holder = new CityViewHolder();
+                    holder.name = (TextView) view.findViewById(R.id.city_name);
+                    holder.time = (TextView) view.findViewById(R.id.city_time);
+                    holder.selected = (CheckBox) view.findViewById(R.id.city_onoff);
+                    view.setTag(holder);
                 }
                 view.setOnClickListener(CitiesActivity.this);
-                TextView name = (TextView) view.findViewById(R.id.city_name);
-                TextView tz = (TextView) view.findViewById(R.id.city_time);
-                CheckBox cb = (CheckBox) view.findViewById(R.id.city_onoff);
-                cb.setTag(c);
-                cb.setChecked(mSelectedCitiesList.containsKey(c.mCityId));
-                cb.setOnCheckedChangeListener(CitiesActivity.this);
+                CityViewHolder holder = (CityViewHolder) view.getTag();
+                holder.selected.setTag(c);
+                holder.selected.setChecked(mUserSelectedCities.containsKey(c.mCityId));
+                holder.selected.setOnCheckedChangeListener(CitiesActivity.this);
                 mCalendar.setTimeZone(TimeZone.getTimeZone(c.mTimeZone));
-                tz.setText(DateFormat.format(mIs24HoursMode ? "k:mm" : "h:mmaa", mCalendar));
-                name.setText(c.mCityName, TextView.BufferType.SPANNABLE);
+                holder.time.setText(DateFormat.format(mIs24HoursMode ? FORMAT_24_HOUR
+                        : FORMAT_12_HOUR, mCalendar));
+                holder.name.setText(c.mCityName, TextView.BufferType.SPANNABLE);
             }
             return view;
         }
 
+        @Override
+        public int getViewTypeCount() {
+            return 2;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return (mDisplayedCitiesList.get(position).mCityId != null)
+                    ? VIEW_TYPE_CITY : VIEW_TYPE_HEADER;
+        }
+
+        private class CityViewHolder {
+            TextView name;
+            TextView time;
+            CheckBox selected;
+        }
+
         public void set24HoursMode(Context c) {
             mIs24HoursMode = Alarms.get24HourMode(c);
             notifyDataSetChanged();
         }
 
-        private void loadCitiesDataBase(Context c) {
-            CityObj[] tempList = Utils.loadCitiesDataBase(c);
-            if (tempList == null) {
-                return;
-            }
-            // Create section indexer and add headers to the cities list
-            String val = null;
-            ArrayList<String> sections = new ArrayList<String>();
-            ArrayList<Integer> positions = new ArrayList<Integer>();
-            ArrayList<CityObj> items = new ArrayList<CityObj>();
-            int count = 0;
-            for (int i = 0; i < tempList.length; i++) {
-                CityObj city = tempList[i];
-                if (city.mCityId.equals(DELETED_ENTRY)) {
-                    continue;
-                }
-                if (!city.mCityName.substring(0, 1).equals(val)) {
-                    val = city.mCityName.substring(0, 1).toUpperCase();
-                    sections.add(val);
-                    positions.add(count);
-                    // Add a header
-                    items.add(new CityObj(val, null, null));
-                    count++;
-                }
-                items.add(city);
-                count++;
-            }
-            mSectionHeaders = sections.toArray();
-            mSectionPositions = positions.toArray();
-            mAllTheCitiesList = items;
-            mDisplayedCitiesList = items;
-        }
-
         @Override
         public int getPositionForSection(int section) {
-            return (mSectionPositions != null) ? (Integer) mSectionPositions[section] : 0;
+            return (mSectionPositions != null) ? mSectionPositions[section] : 0;
         }
 
+
         @Override
         public int getSectionForPosition(int p) {
-            if (mSectionPositions != null) {
-                for (int i = 0; i < mSectionPositions.length - 1; i++) {
-                    if (p >= (Integer) mSectionPositions[i]
-                            && p < (Integer) mSectionPositions[i + 1]) {
+            final Integer[] positions = mSectionPositions;
+            if (positions != null && positions.length > 0) {
+                for (int i = 0; i < positions.length - 1; i++) {
+                    if (p >= positions[i]
+                            && p < positions[i + 1]) {
                         return i;
                     }
                 }
-                if (p >= (Integer) mSectionPositions[mSectionPositions.length - 1]) {
-                    return mSectionPositions.length - 1;
+                if (p >= positions[positions.length - 1]) {
+                    return positions.length - 1;
                 }
             }
             return 0;
@@ -277,9 +380,13 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mFactory = LayoutInflater.from(this);
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        mSortType = mPrefs.getInt(PREF_SORT, SORT_BY_NAME);
+        mSelectedCitiesHeaderString = getString(R.string.selected_cities_label);
         if (savedInstanceState != null) {
             mQueryTextBuffer.append(savedInstanceState.getString(KEY_SEARCH_QUERY));
             mSearchMode = savedInstanceState.getBoolean(KEY_SEARCH_MODE);
+            mPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
         }
         updateLayout();
     }
@@ -289,17 +396,17 @@
         super.onSaveInstanceState(bundle);
         bundle.putString(KEY_SEARCH_QUERY, mQueryTextBuffer.toString());
         bundle.putBoolean(KEY_SEARCH_MODE, mSearchMode);
+        bundle.putInt(KEY_LIST_POSITION, mCitiesList.getFirstVisiblePosition());
     }
 
     private void updateLayout() {
         setContentView(R.layout.cities_activity);
         mCitiesList = (ListView) findViewById(R.id.cities_list);
-        mCitiesList.setFastScrollAlwaysVisible(true);
-        mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString()));
+        mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
         mCitiesList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
         mUserSelectedCities = Cities.readCitiesFromSharedPrefs(
                 PreferenceManager.getDefaultSharedPreferences(this));
-        mAdapter = new CityAdapter(this, mUserSelectedCities, mFactory);
+        mAdapter = new CityAdapter(this, mFactory);
         mCitiesList.setAdapter(mAdapter);
         ActionBar actionBar = getActionBar();
         if (actionBar != null) {
@@ -340,6 +447,13 @@
                     }
                 }
                 return true;
+            case R.id.menu_item_sort:
+                if (mAdapter != null) {
+                    mAdapter.toggleSort();
+                    mCitiesList.setFastScrollEnabled(
+                            TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
+                }
+                return true;
             case android.R.id.home:
                 Intent intent = new Intent(this, DeskClock.class);
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -359,8 +473,8 @@
             Utils.prepareHelpMenuItem(this, help);
         }
 
-        mSearchMenu = menu.findItem(R.id.menu_item_search);
-        mSearchView = (SearchView) mSearchMenu.getActionView();
+        MenuItem searchMenu = menu.findItem(R.id.menu_item_search);
+        mSearchView = (SearchView) searchMenu.getActionView();
         mSearchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
         mSearchView.setOnSearchClickListener(new OnClickListener() {
 
@@ -389,6 +503,17 @@
     }
 
     @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem sortMenuItem = menu.findItem(R.id.menu_item_sort);
+        if (mSortType == SORT_BY_NAME) {
+            sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_gmt_offset));
+        } else {
+            sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_name));
+        }
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
     public void onCheckedChanged(CompoundButton b, boolean checked) {
         CityObj c = (CityObj) b.getTag();
         if (checked) {
@@ -404,13 +529,14 @@
         boolean checked = b.isChecked();
         onCheckedChanged(b, checked);
         b.setChecked(!checked);
+        mAdapter.refreshSelectedCities();
     }
 
     @Override
     public boolean onQueryTextChange(String queryText) {
         mQueryTextBuffer.setLength(0);
         mQueryTextBuffer.append(queryText);
-        mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(queryText.trim()));
+        mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim()));
         mAdapter.getFilter().filter(queryText);
         return true;
     }
diff --git a/src/com/android/deskclock/worldclock/CityGmtOffsetComparator.java b/src/com/android/deskclock/worldclock/CityGmtOffsetComparator.java
new file mode 100644
index 0000000..f28b1cf
--- /dev/null
+++ b/src/com/android/deskclock/worldclock/CityGmtOffsetComparator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.deskclock.worldclock;
+
+import java.util.Comparator;
+import java.util.TimeZone;
+
+public class CityGmtOffsetComparator implements Comparator<CityObj> {
+
+    private CityNameComparator mNameComparator = null;
+
+    @Override
+    public int compare(CityObj c1, CityObj c2) {
+        int offset = TimeZone.getTimeZone(c1.mTimeZone).getRawOffset();
+        int offset2 = TimeZone.getTimeZone(c2.mTimeZone).getRawOffset();
+        return (offset < offset2) ? -1 : (offset > offset2) ? 1 :
+            getCityNameComparator().compare(c1, c2);
+    }
+
+    private CityNameComparator getCityNameComparator() {
+        if (mNameComparator == null) {
+            mNameComparator = new CityNameComparator();
+        }
+        return mNameComparator;
+    }
+}
diff --git a/src/com/android/deskclock/worldclock/CityNameComparator.java b/src/com/android/deskclock/worldclock/CityNameComparator.java
new file mode 100644
index 0000000..f6b7bb1
--- /dev/null
+++ b/src/com/android/deskclock/worldclock/CityNameComparator.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.deskclock.worldclock;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+public class CityNameComparator implements Comparator<CityObj> {
+
+    @Override
+    public int compare(CityObj c1, CityObj c2) {
+        return Collator.getInstance().compare(c1.mCityName, c2.mCityName);
+    }
+}
diff --git a/src/com/android/deskclock/worldclock/WorldClockAdapter.java b/src/com/android/deskclock/worldclock/WorldClockAdapter.java
index 56210b6..4c5429f 100644
--- a/src/com/android/deskclock/worldclock/WorldClockAdapter.java
+++ b/src/com/android/deskclock/worldclock/WorldClockAdapter.java
@@ -75,7 +75,9 @@
         // Read the cities DB so that the names and timezones will be taken from the DB
         // and not from the selected list so that change of locale or changes in the DB will
         // be reflected.
-        CityObj[] cities = Utils.loadCitiesDataBase(context);
+        CityObj[] cities = Utils.loadCitiesFromXml(context);
+        // Sort alphabetically
+        Arrays.sort(cities, new CityNameComparator());
         if (cities != null) {
             for (int i = 0; i < cities.length; i ++) {
                 mCitiesDb.put(cities[i].mCityId, cities [i]);