am f094f3ef: (-s ours) am bc440662: Import revised translations.  DO NOT MERGE

Merge commit 'f094f3ef2fab1b4ecb1b06b5003b212b294a0752'

* commit 'f094f3ef2fab1b4ecb1b06b5003b212b294a0752':
  Import revised translations.  DO NOT MERGE
diff --git a/Android.mk b/Android.mk
index 0b8f2a9..4d9e73f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,6 +1,8 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_EMMA_COVERAGE_FILTER := +com.android.calendar.*
+
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
@@ -9,7 +11,7 @@
 # library.
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES += android-common
+LOCAL_STATIC_JAVA_LIBRARIES += android-common guava
 
 LOCAL_PACKAGE_NAME := Calendar
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e77ee05..e8f97e4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
     <uses-permission android:name="android.permission.WRITE_CALENDAR" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
 
     <application android:name="CalendarApplication"
@@ -43,8 +44,13 @@
              (android.test) library. -->
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name="LaunchActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity
+            android:name="AllInOneActivity"
+            android:theme="@style/CalendarTheme.WithActionBar">
+        </activity>
+
+        <activity-alias android:name="LaunchActivity"
+            android:targetActivity=".AllInOneActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -57,31 +63,15 @@
                 <data android:host="com.android.calendar" />
                 <data android:scheme="content"/>
             </intent-filter>
-        </activity>
-
-        <activity android:name="MonthActivity" android:label="@string/month_view"
-            android:theme="@style/CalendarTheme" />
-        <activity android:name="WeekActivity" android:label="@string/week_view"
-            android:theme="@style/CalendarTheme" />
-        <activity android:name="DayActivity" android:label="@string/day_view"
-            android:theme="@style/CalendarTheme" />
-        <activity android:name="AgendaActivity" android:label="@string/agenda_view"
-            android:theme="@android:style/Theme.Light"
-            android:exported="true" />
-
-        <activity android:name="EditEvent" android:label="@string/event_edit_title"
-            android:theme="@android:style/Theme"
-            android:configChanges="orientation|keyboardHidden">
-
             <intent-filter>
                 <action android:name="android.intent.action.EDIT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.cursor.item/event" />
             </intent-filter>
-        </activity>
+        </activity-alias>
 
         <activity android:name="EventInfoActivity" android:label="@string/event_info_title"
-            android:theme="@android:style/Theme.Light"
+            android:theme="@style/CalendarTheme.WithActionBar"
             android:configChanges="orientation|keyboardHidden">
 
             <intent-filter>
@@ -107,10 +97,30 @@
         </activity>
 
         <activity android:name="SelectCalendarsActivity" android:label="@string/calendars_title" />
-        <activity android:name="CalendarPreferenceActivity" android:label="@string/preferences_title" />
-        <activity android:name="AlertActivity" android:launchMode="singleInstance"
+        <activity android:name="CalendarSettingsActivity" android:label="@string/preferences_title"
+            android:theme="@android:style/Theme.Light.Holo"/>
+
+        <!-- Declarations for search -->
+        <!-- Make all activities a searchable context -->
+        <meta-data android:name="android.app.default_searchable"
+            android:value="com.android.calendar.SearchActivity"/>
+
+        <activity android:name="SearchActivity" android:label="@string/search_title"
+            android:launchMode="singleTop" android:theme="@style/CalendarTheme.WithActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH"/>
+            </intent-filter>
+            <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
+        </activity>
+
+        <provider android:name=".CalendarRecentSuggestionsProvider"
+            android:authorities="com.android.calendar.CalendarRecentSuggestionsProvider" />
+
+        <!-- Declarations for alerts/reminders -->
+        <activity android:name=".alerts.AlertActivity" android:launchMode="singleInstance"
              android:theme="@android:style/Theme.Light" android:excludeFromRecents="true" />
-        <receiver android:name="AlertReceiver">
+
+        <receiver android:name=".alerts.AlertReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.EVENT_REMINDER" />
                 <action android:name="android.intent.action.LOCALE_CHANGED" />
@@ -120,9 +130,32 @@
             </intent-filter>
         </receiver>
 
-        <service android:name="AlertService" />
+        <service android:name=".alerts.AlertService" />
 
-        <service android:name="DismissAllAlarmsService" />
+        <service android:name=".alerts.DismissAllAlarmsService" />
+
+        <!-- Declarations for the widget -->
+        <receiver android:name=".widget.CalendarAppWidgetReceiver" android:enabled="false">
+            <intent-filter>
+                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+                <action android:name="android.intent.action.TIME_SET" />
+                <action android:name="android.intent.action.DATE_CHANGED" />
+            </intent-filter>
+            <intent-filter>
+                <data android:scheme="content" android:host="com.android.calendar"/>
+                <action android:name="android.intent.action.PROVIDER_CHANGED"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".widget.CalendarAppWidgetProvider" android:label="@string/gadget_title">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="com.android.calendar.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
+        </receiver>
+
+        <service android:name=".widget.CalendarAppWidgetService" android:exported="true"/>
 
         <activity android:name="CalendarTests" android:label="Calendar Tests">
             <intent-filter>
@@ -130,6 +163,9 @@
                  <category android:name="android.intent.category.UNIT_TEST" />
             </intent-filter>
         </activity>
+
+        <service android:name=".AsyncQueryServiceHelper" />
+
     </application>
 </manifest>
 
diff --git a/res/anim/slide_in_fade.xml b/res/anim/slide_in_fade.xml
new file mode 100644
index 0000000..72fc56b
--- /dev/null
+++ b/res/anim/slide_in_fade.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromYDelta="100%p" android:toYDelta="0"
+        android:duration="@integer/slide_transition_duration"/>
+    <alpha
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@integer/slide_transition_duration" />
+</set>
\ No newline at end of file
diff --git a/res/anim/slide_out_fade.xml b/res/anim/slide_out_fade.xml
new file mode 100644
index 0000000..52681a7
--- /dev/null
+++ b/res/anim/slide_out_fade.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromYDelta="0" android:toYDelta="-100%p"
+        android:duration="@integer/slide_transition_duration"/>
+    <alpha
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromAlpha="1.0" android:toAlpha="0"
+        android:duration="@integer/slide_transition_duration" />
+</set>
\ No newline at end of file
diff --git a/res/drawable-hdpi/appwidget_bg.9.png b/res/drawable-hdpi/appwidget_bg.9.png
new file mode 100644
index 0000000..d9af8fb
--- /dev/null
+++ b/res/drawable-hdpi/appwidget_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_bg_focus.9.png b/res/drawable-hdpi/appwidget_bg_focus.9.png
new file mode 100644
index 0000000..ee098af
--- /dev/null
+++ b/res/drawable-hdpi/appwidget_bg_focus.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_bg_press.9.png b/res/drawable-hdpi/appwidget_bg_press.9.png
new file mode 100644
index 0000000..03ca2a1
--- /dev/null
+++ b/res/drawable-hdpi/appwidget_bg_press.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_btn_round_plus.png b/res/drawable-hdpi/appwidget_btn_round_plus.png
new file mode 100644
index 0000000..0d812e9
--- /dev/null
+++ b/res/drawable-hdpi/appwidget_btn_round_plus.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_calendar_bgtop_blue.9.png b/res/drawable-hdpi/appwidget_calendar_bgtop_blue.9.png
new file mode 100644
index 0000000..c55f808
--- /dev/null
+++ b/res/drawable-hdpi/appwidget_calendar_bgtop_blue.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_refresh.png b/res/drawable-hdpi/ic_menu_refresh.png
new file mode 100644
index 0000000..53cacca
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_refresh.png
Binary files differ
diff --git a/res/drawable-hdpi/month_view_longpress.9.png b/res/drawable-hdpi/month_view_longpress.9.png
new file mode 100644
index 0000000..aa8179d
--- /dev/null
+++ b/res/drawable-hdpi/month_view_longpress.9.png
Binary files differ
diff --git a/res/drawable-hdpi/month_view_pressed.9.png b/res/drawable-hdpi/month_view_pressed.9.png
new file mode 100644
index 0000000..0868dfd
--- /dev/null
+++ b/res/drawable-hdpi/month_view_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/month_view_selected.9.png b/res/drawable-hdpi/month_view_selected.9.png
new file mode 100644
index 0000000..e3236ad
--- /dev/null
+++ b/res/drawable-hdpi/month_view_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/month_view_today_background.9.png b/res/drawable-hdpi/month_view_today_background.9.png
new file mode 100644
index 0000000..c4f8560
--- /dev/null
+++ b/res/drawable-hdpi/month_view_today_background.9.png
Binary files differ
diff --git a/res/drawable-hdpi/white_list_rule_cal.9.png b/res/drawable-hdpi/white_list_rule_cal.9.png
new file mode 100755
index 0000000..a4d54cf
--- /dev/null
+++ b/res/drawable-hdpi/white_list_rule_cal.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_bg.9.png b/res/drawable-mdpi/appwidget_bg.9.png
new file mode 100644
index 0000000..8049191
--- /dev/null
+++ b/res/drawable-mdpi/appwidget_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_bg_focus.9.png b/res/drawable-mdpi/appwidget_bg_focus.9.png
new file mode 100644
index 0000000..f4bbb08
--- /dev/null
+++ b/res/drawable-mdpi/appwidget_bg_focus.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_bg_press.9.png b/res/drawable-mdpi/appwidget_bg_press.9.png
new file mode 100644
index 0000000..d060b77
--- /dev/null
+++ b/res/drawable-mdpi/appwidget_bg_press.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_btn_round_plus.png b/res/drawable-mdpi/appwidget_btn_round_plus.png
new file mode 100644
index 0000000..2792602
--- /dev/null
+++ b/res/drawable-mdpi/appwidget_btn_round_plus.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_calendar_bgtop_blue.9.png b/res/drawable-mdpi/appwidget_calendar_bgtop_blue.9.png
new file mode 100644
index 0000000..af0f466
--- /dev/null
+++ b/res/drawable-mdpi/appwidget_calendar_bgtop_blue.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_refresh.png b/res/drawable-mdpi/ic_menu_refresh.png
new file mode 100644
index 0000000..77d70dd
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_refresh.png
Binary files differ
diff --git a/res/drawable-mdpi/month_view_longpress.9.png b/res/drawable-mdpi/month_view_longpress.9.png
new file mode 100644
index 0000000..e84b4fa
--- /dev/null
+++ b/res/drawable-mdpi/month_view_longpress.9.png
Binary files differ
diff --git a/res/drawable-mdpi/month_view_pressed.9.png b/res/drawable-mdpi/month_view_pressed.9.png
new file mode 100644
index 0000000..70c0f94
--- /dev/null
+++ b/res/drawable-mdpi/month_view_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/month_view_selected.9.png b/res/drawable-mdpi/month_view_selected.9.png
new file mode 100644
index 0000000..67a6e2e
--- /dev/null
+++ b/res/drawable-mdpi/month_view_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/month_view_today_background.9.png b/res/drawable-mdpi/month_view_today_background.9.png
new file mode 100644
index 0000000..c56249f
--- /dev/null
+++ b/res/drawable-mdpi/month_view_today_background.9.png
Binary files differ
diff --git a/res/drawable-mdpi/white_list_rule_cal.9.png b/res/drawable-mdpi/white_list_rule_cal.9.png
new file mode 100644
index 0000000..11cb7ab
--- /dev/null
+++ b/res/drawable-mdpi/white_list_rule_cal.9.png
Binary files differ
diff --git a/res/drawable/appwidget_background.xml b/res/drawable/appwidget_background.xml
new file mode 100644
index 0000000..abbb9e6
--- /dev/null
+++ b/res/drawable/appwidget_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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_window_focused="false" android:drawable="@drawable/appwidget_bg" />
+    <item android:state_pressed="true" android:drawable="@drawable/appwidget_bg_press" />
+    <item android:state_focused="true" android:drawable="@drawable/appwidget_bg_focus" />
+    <item android:drawable="@drawable/appwidget_bg" />
+</selector>
diff --git a/res/layout-land/all_in_one.xml b/res/layout-land/all_in_one.xml
new file mode 100644
index 0000000..83f3cad
--- /dev/null
+++ b/res/layout-land/all_in_one.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/mini_month"
+
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+
+        android:layout_height="300dip"
+        android:layout_width="350dip" />
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_below="@id/mini_month"
+        android:layout_alignRight="@id/mini_month"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" >
+
+        <FrameLayout
+            android:id="@+id/calendar_list"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:layout_weight="1" />
+
+        <TextView
+            android:id="@+id/home_time"
+            android:layout_below="@id/calendar_list"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:gravity="center"
+            style="?android:attr/textAppearanceMedium"
+            android:visibility="gone" />
+
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/main_pane"
+
+        android:layout_toRightOf="@id/mini_month"
+        android:layout_alignParentRight="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+</RelativeLayout>
diff --git a/res/layout-land/search.xml b/res/layout-land/search.xml
new file mode 100644
index 0000000..0359b7c
--- /dev/null
+++ b/res/layout-land/search.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/event_info"
+
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="700dip" />
+
+    <FrameLayout
+        android:id="@+id/search_results"
+
+        android:layout_toLeftOf="@id/event_info"
+        android:layout_alignParentLeft="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+</RelativeLayout>
diff --git a/res/layout/account_calendars.xml b/res/layout/account_calendars.xml
new file mode 100644
index 0000000..86b01f8
--- /dev/null
+++ b/res/layout/account_calendars.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/calendars"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true">
+
+    <TextView android:id="@+id/account_status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/textAppearanceMedium" />
+
+    <Button android:id="@+id/sync_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="left|center_vertical"
+        android:text="@android:string/ok" />
+
+    <include android:id="@+id/calendar_list" layout="@android:layout/list_content"/>
+
+</LinearLayout>
diff --git a/res/layout/agenda_day.xml b/res/layout/agenda_day.xml
index b2b4a1c..2653066 100644
--- a/res/layout/agenda_day.xml
+++ b/res/layout/agenda_day.xml
@@ -4,9 +4,9 @@
      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.
diff --git a/res/layout/agenda_item.xml b/res/layout/agenda_item.xml
index d650f1e..b8ade61 100644
--- a/res/layout/agenda_item.xml
+++ b/res/layout/agenda_item.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.calendar.AgendaItemView
+<com.android.calendar.agenda.AgendaItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@android:id/content"
     android:layout_height="wrap_content"
@@ -57,4 +57,4 @@
         android:textColor="?android:attr/textColorSecondary"
         style="?android:attr/textAppearanceSmallInverse" />
 
-</com.android.calendar.AgendaItemView>
+</com.android.calendar.agenda.AgendaItemView>
diff --git a/res/layout/all_in_one.xml b/res/layout/all_in_one.xml
new file mode 100644
index 0000000..0e2e41f
--- /dev/null
+++ b/res/layout/all_in_one.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/mini_month"
+
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+
+        android:layout_height="300dip"
+        android:layout_width="350dip" />
+
+    <LinearLayout
+        android:layout_toLeftOf="@id/mini_month"
+        android:orientation="vertical"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignBottom="@id/mini_month"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" >
+
+        <FrameLayout
+            android:id="@+id/calendar_list"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:layout_weight="1" />
+
+        <TextView
+            android:id="@+id/home_time"
+            android:layout_below="@id/calendar_list"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:gravity="center"
+            style="?android:attr/textAppearanceMedium"
+            android:visibility="gone" />
+
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/main_pane"
+
+        android:layout_below="@id/mini_month"
+        android:layout_alignParentBottom="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+</RelativeLayout>
diff --git a/res/layout/appwidget.xml b/res/layout/appwidget.xml
new file mode 100644
index 0000000..08b1cb4
--- /dev/null
+++ b/res/layout/appwidget.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/appwidget"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@drawable/appwidget_background"
+    android:focusable="true"
+    android:clickable="true">
+
+    <!-- Header -->
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="@drawable/appwidget_calendar_bgtop_blue">
+
+        <ImageView
+            android:id="@+id/widget_logo"
+            android:src="@drawable/app_icon"
+            android:layout_width="50dip"
+            android:layout_height="50dip"
+            android:layout_marginLeft="5dip"
+            android:layout_marginRight="10dip"
+            android:scaleType="centerInside" />
+
+        <LinearLayout
+            android:id="@+id/header_day"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="horizontal">
+            <TextView
+                android:id="@+id/day_of_week"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:layout_marginLeft="7dip"
+                android:layout_marginRight="7dip"
+                android:layout_marginBottom="5dip"
+                android:textColor="@color/appwidget_date"
+                android:textSize="20sp"
+                android:gravity="left|bottom"
+                android:singleLine="true" />
+
+            <TextView
+                android:id="@+id/day_of_month"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="7dip"
+                android:layout_marginRight="7dip"
+                android:layout_marginBottom="5dip"
+                android:gravity="right|bottom"
+                android:textColor="@color/appwidget_date"
+                android:textSize="22sp"
+                android:textStyle="bold"
+                android:singleLine="true" />
+        </LinearLayout>
+
+        <ImageButton
+            android:id="@+id/new_event_button"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:src="@drawable/appwidget_btn_round_plus"
+            android:background="#0000" />
+    </LinearLayout>
+
+    <!-- Events container -->
+    <ListView
+        android:id="@+id/events_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:cacheColorHint="#00000000"/>
+
+</LinearLayout>
diff --git a/res/layout/appwidget_day.xml b/res/layout/appwidget_day.xml
new file mode 100644
index 0000000..625212a
--- /dev/null
+++ b/res/layout/appwidget_day.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/appwidget_date"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:addStatesFromChildren="true"
+    android:focusableInTouchMode="false">
+
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/date"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:background="@color/agenda_day_bar_color"
+        android:padding="4dip"
+        android:textStyle="bold"
+        android:textColor="@color/appwidget_title"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/appwidget_loading.xml b/res/layout/appwidget_loading.xml
new file mode 100644
index 0000000..ead5a54
--- /dev/null
+++ b/res/layout/appwidget_loading.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/appwidget_loading"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:addStatesFromChildren="true">
+
+    <!-- Loading -->
+    <TextView
+        android:id="@+id/loading"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="10dip"
+        android:padding="7dip"
+        android:gravity="center"
+        android:textSize="14sp"
+        android:textStyle="bold"
+        android:textColor="@color/appwidget_no_events"
+        android:text="@string/loading" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/appwidget_no_events.xml b/res/layout/appwidget_no_events.xml
new file mode 100644
index 0000000..e7303a6
--- /dev/null
+++ b/res/layout/appwidget_no_events.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/appwidget_no_events"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:addStatesFromChildren="true">
+
+    <!-- No events -->
+    <TextView
+        android:id="@+id/no_events"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="10dip"
+        android:padding="7dip"
+        android:gravity="center"
+        android:textSize="14sp"
+        android:textStyle="bold"
+        android:textColor="@color/appwidget_no_events"
+        android:text="@string/gadget_no_events" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/appwidget_row.xml b/res/layout/appwidget_row.xml
new file mode 100644
index 0000000..cdbc51d
--- /dev/null
+++ b/res/layout/appwidget_row.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/appwidget_row"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:addStatesFromChildren="true">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextAppearance.WidgetTitle" />
+
+    <TextView
+        android:id="@+id/when"
+        style="@style/TextAppearance.WidgetWhen" />
+
+    <TextView
+        android:id="@+id/where"
+        style="@style/TextAppearance.WidgetWhere" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/calendar_sync_item.xml b/res/layout/calendar_sync_item.xml
new file mode 100644
index 0000000..f43ae82
--- /dev/null
+++ b/res/layout/calendar_sync_item.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:layout_marginRight="6dip"
+    android:layout_marginTop="6dip"
+    android:layout_marginBottom="6dip"
+    android:layout_weight="1">
+
+    <View android:id="@+id/color"
+        android:layout_width="10dip"
+        android:layout_height="58dip"
+        android:layout_marginRight="4dip"
+    />
+
+    <CheckBox android:id="@+id/sync"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="4dip"
+        android:layout_marginRight="14dip"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+    />
+
+    <TextView android:id="@+id/calendar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/color"
+        android:layout_toLeftOf="@id/sync"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        style="?android:attr/textAppearanceLarge"
+        android:duplicateParentState="true"
+    />
+
+    <TextView android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/calendar"
+        android:layout_alignLeft="@id/calendar"
+        android:layout_toLeftOf="@id/sync"
+        android:ellipsize="marquee"
+        android:maxLines="2"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:duplicateParentState="true"
+    />
+
+</RelativeLayout>
diff --git a/res/layout/calendars_activity.xml b/res/layout/calendars_activity.xml
deleted file mode 100644
index 4f6d423..0000000
--- a/res/layout/calendars_activity.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/calendars"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ExpandableListView android:id="@android:id/list"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:clickable="true"
-        android:layout_weight="1" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        style="@android:style/ButtonBar">
-
-        <Button
-            android:id="@+id/btn_done"
-            android:layout_width="0dip"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:text="@android:string/ok" />
-
-        <Button
-            android:id="@+id/btn_discard"
-            android:layout_width="0dip"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:text="@android:string/cancel" />
-
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/contact_item.xml b/res/layout/contact_item.xml
index 524e237..d8b840c 100644
--- a/res/layout/contact_item.xml
+++ b/res/layout/contact_item.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:paddingLeft="0dip"
-    android:paddingRight="9dip"
+    android:paddingRight="0dip"
     android:minHeight="48dip">
 
     <QuickContactBadge
@@ -37,25 +37,35 @@
         android:src="@drawable/ic_contact_picture"
         style="?android:attr/quickContactBadgeStyleWindowMedium" />
 
-    <TextView
-        android:id="@+id/name"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:inputType="none"
-        android:paddingLeft="2dip"
-        android:layout_centerVertical="true"
-        android:layout_toRightOf="@id/badge"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
     <ImageView
         android:id="@+id/presence"
         android:scaleType="fitXY"
         android:visibility="gone"
-        android:layout_alignParentRight="true"
+        android:layout_toRightOf="@id/badge"
         android:layout_centerVertical="true"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 
+    <ImageButton android:id="@+id/contact_remove"
+        style="@style/MinusButton"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true" />
+
+    <TextView
+        android:id="@+id/name"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:inputType="none"
+        android:paddingLeft="2dip"
+        android:layout_centerVertical="true"
+        android:layout_toRightOf="@id/presence"
+        android:layout_toLeftOf="@id/contact_remove"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.EditEvent_Label"/>
+
     <View
         android:id="@+id/separator"
         android:layout_width="match_parent"
diff --git a/res/layout/day_activity.xml b/res/layout/day_activity.xml
index 967da8e..d04089c 100644
--- a/res/layout/day_activity.xml
+++ b/res/layout/day_activity.xml
@@ -14,44 +14,14 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <RelativeLayout
+    android:layout_height="0dip"
+    android:layout_weight="1.0"
+    android:foregroundGravity="fill_horizontal|top">
+    <ViewSwitcher
+        android:id="@+id/switcher"
         android:layout_width="match_parent"
-        android:layout_height="?android:attr/windowTitleSize"
-        style="?android:attr/windowTitleBackgroundStyle">
-
-        <TextView android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            style="?android:attr/windowTitleStyle"
-            android:background="@null"
-            android:fadingEdge="horizontal"
-        />
-
-        <ProgressBar android:id="@+id/progress_circular"
-            style="?android:attr/progressBarStyleSmall"
-            android:visibility="gone"
-            android:max="10000"
-            android:layout_centerVertical="true"
-            android:layout_alignParentRight="true"
-            android:layout_marginLeft="6dip"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </RelativeLayout>
-
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1.0"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="@*android:drawable/title_bar_shadow">
-        <ViewSwitcher android:id="@+id/switcher"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-    </FrameLayout>
-</LinearLayout>
+        android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/res/layout/edit_event.xml b/res/layout/edit_event.xml
index aabd602..ab1f1ca 100644
--- a/res/layout/edit_event.xml
+++ b/res/layout/edit_event.xml
@@ -14,318 +14,206 @@
      limitations under the License.
 -->
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/scroll_view"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/edit_event"
+    android:orientation="vertical"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    style="@style/EditEvent_Layout">
 
-    <LinearLayout android:id="@+id/event"
-        android:background="#fafafa"
-        android:orientation="vertical"
+    <TextView android:id="@+id/loading_message"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:text="@string/loading" />
 
-        <!-- WHAT -->
-        <LinearLayout
-            android:orientation="vertical"
+    <LinearLayout android:id="@+id/two_pane"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+
+        <ScrollView
+            android:id="@+id/scroll_view"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:fillViewport="true"
+            android:visibility="gone">
 
-            <TextView android:id="@+id/what_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/what_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <EditText android:id="@+id/title"
+            <LinearLayout android:id="@+id/event"
+                android:orientation="vertical"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="@string/hint_what"
-                android:capitalize="sentences"
-                android:contentDescription="@string/what_label"/>
-        </LinearLayout>
+                android:layout_height="match_parent">
 
-        <!-- WHEN -->
-        <LinearLayout android:id="@+id/when_container"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
+                <!-- CALENDARS -->
+                <LinearLayout android:id="@+id/calendar_group"
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
 
-            <TextView android:id="@+id/from_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/edit_event_from_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/edit_event_calendar_label"
+                        style="@style/TextAppearance.EditEvent_Label"/>
 
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
+                    <Spinner android:id="@+id/calendars"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:prompt="@string/edit_event_calendar_label"/>
 
-                <Button android:id="@+id/start_date"
-                    android:layout_width="0px"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="7"
-                    android:gravity="left|center_vertical"
-                    android:contentDescription="@string/content_description_edit_event_from_date"
-                    style="?android:attr/textAppearanceMediumInverse"/>
+                </LinearLayout>
 
-                <Button android:id="@+id/start_time"
-                    android:layout_width="0px"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="4"
-                    android:gravity="left|center_vertical"
-                    android:contentDescription="@string/content_description_edit_event_from_time"
-                    style="?android:attr/textAppearanceMediumInverse"/>
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingRight="3dip"
-                android:paddingLeft="3dip" >
-
-                <TextView android:id="@+id/start_date_home"
+                <!-- WHAT -->
+                <TextView android:id="@+id/what_label"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:gravity="left"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.EditEvent_homeTime"/>
+                    android:text="@string/what_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
 
-                <TextView android:id="@+id/start_time_home"
+                <EditText android:id="@+id/title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/hint_what"
+                    android:capitalize="sentences"/>
+
+                <!-- WHEN -->
+                <TextView android:id="@+id/from_label"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:gravity="right"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.EditEvent_homeTime"/>
+                    android:text="@string/edit_event_from_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
 
-            </LinearLayout>
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
 
-            <TextView android:id="@+id/to_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/edit_event_to_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
+                    <Button android:id="@+id/start_date"
+                        android:layout_width="0px"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="7"
+                        android:gravity="left|center_vertical"
+                        style="?android:attr/textAppearanceMedium"/>
 
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
+                    <Button android:id="@+id/start_time"
+                        android:layout_width="0px"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="4"
+                        android:gravity="left|center_vertical"
+                        style="?android:attr/textAppearanceMedium"/>
 
-                <Button android:id="@+id/end_date"
-                    android:layout_width="0px"
+                </LinearLayout>
+
+                <TextView android:id="@+id/to_label"
+                    android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_weight="7"
+                    android:text="@string/edit_event_to_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <Button android:id="@+id/end_date"
+                        android:layout_width="0px"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="7"
+                        android:gravity="left|center_vertical"
+                        style="?android:attr/textAppearanceMedium"/>
+
+                    <Button android:id="@+id/end_time"
+                        android:layout_width="0px"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="4"
+                        android:gravity="left|center_vertical"
+                        style="?android:attr/textAppearanceMedium"/>
+
+                </LinearLayout>
+
+                <TextView android:id="@+id/timezone_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/timezone_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+
+                <Button android:id="@+id/timezone"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
                     android:gravity="left|center_vertical"
-                    android:contentDescription="@string/content_description_edit_event_to_date"
-                    style="?android:attr/textAppearanceMediumInverse"/>
+                    style="?android:attr/textAppearanceMedium"/>
 
-                <Button android:id="@+id/end_time"
-                    android:layout_width="0px"
+                <LinearLayout
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_weight="4"
-                    android:gravity="left|center_vertical"
-                    android:contentDescription="@string/content_description_edit_event_to_time"
-                    style="?android:attr/textAppearanceMediumInverse"/>
-            </LinearLayout>
+                    android:gravity="center_vertical|right"
+                    android:paddingBottom="5dip">
 
-            <LinearLayout
-                android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingRight="3dip"
-                android:paddingLeft="3dip" >
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/edit_event_all_day_label"
+                        android:paddingTop="1dip"
+                        android:paddingRight="7dip"
+                        style="?android:attr/textAppearanceMedium"/>
 
-                <TextView android:id="@+id/end_date_home"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:gravity="left"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.EditEvent_homeTime"/>
+                    <CheckBox android:id="@+id/is_all_day"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:paddingTop="15dip"
+                        android:paddingBottom="15dip"
+                        android:paddingRight="1dip"/>
 
-                <TextView android:id="@+id/end_time_home"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:gravity="right"
-                    android:visibility="gone"
-                    style="@style/TextAppearance.EditEvent_homeTime"/>
+                </LinearLayout>
 
-            </LinearLayout>
+                <!-- WHERE -->
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dip"
+                    android:background="@android:drawable/divider_horizontal_bright" />
 
-            <TextView android:id="@+id/timezone_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/timezone_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <Button android:id="@+id/timezone"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="left|center_vertical"
-                android:contentDescription="@string/timezone_label"
-                style="?android:attr/textAppearanceMediumInverse"/>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical|right"
-                android:paddingBottom="5dip">
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/edit_event_all_day_label"
-                    android:paddingTop="1dip"
-                    android:paddingRight="7dip"
-                    style="?android:attr/textAppearanceMediumInverse"/>
-                <CheckBox android:id="@+id/is_all_day"
+                    android:text="@string/where_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+
+                <EditText android:id="@+id/location"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/hint_where"
+                    android:capitalize="sentences"/>
+
+                <!-- DESCRIPTION -->
+                <TextView android:id="@+id/description_label"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:paddingTop="15dip"
-                    android:paddingBottom="15dip"
-                    android:paddingRight="1dip"
-                    android:contentDescription="@string/edit_event_all_day_label"/>
-            </LinearLayout>
-        </LinearLayout>
+                    android:text="@string/description_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
 
-        <!-- WHERE -->
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dip"
-            android:background="@android:drawable/divider_horizontal_bright"
-        />
-        <LinearLayout android:id="@+id/where_container"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
+                <EditText android:id="@+id/description"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/hint_description"
+                    android:capitalize="sentences"/>
 
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/where_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
+                <!-- REPEATS -->
+                <TextView android:id="@+id/repeats_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/repeats_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
 
-            <EditText android:id="@+id/location"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="@string/hint_where"
-                android:capitalize="sentences"
-                android:contentDescription="@string/where_label"/>
-        </LinearLayout>
+                <Spinner android:id="@+id/repeats"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:prompt="@string/repeats_label"/>
 
-        <!-- DESCRIPTION -->
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
-
-            <TextView android:id="@+id/description_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/description_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <EditText android:id="@+id/description"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="@string/hint_description"
-                android:capitalize="sentences"
-                android:contentDescription="@string/description_label"/>
-        </LinearLayout>
-
-        <!-- CALENDARS -->
-        <LinearLayout android:id="@+id/calendar_group"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
-
-            <View
-                android:layout_width="match_parent"
-                android:layout_height="1dip"
-                android:background="@android:drawable/divider_horizontal_dark"
-            />
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/edit_event_calendar_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <Spinner android:id="@+id/calendars"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:prompt="@string/edit_event_calendar_label"
-                android:contentDescription="@string/edit_event_calendar_label"/>
-        </LinearLayout>
-
-        <!-- GUESTS/ATTENDEES -->
-        <LinearLayout android:id="@+id/attendees_group"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/attendees_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <MultiAutoCompleteTextView android:id="@+id/attendees"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:inputType="textEmailAddress|textMultiLine"
-                android:hint="@string/hint_attendees"
-                android:layout_marginTop="6dip"
-                android:layout_marginBottom="6dip"
-                android:imeOptions="actionNext"
-                android:contentDescription="@string/attendees_label"/>
-        </LinearLayout>
-
-        <!-- REPEATS -->
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
-
-            <TextView android:id="@+id/repeats_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/repeats_label"
-                style="@style/TextAppearance.EditEvent_Label"/>
-
-            <Spinner android:id="@+id/repeats"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:prompt="@string/repeats_label"
-                android:contentDescription="@string/repeats_label"/>
-        </LinearLayout>
-
-        <!-- MORE OPTIONS -->
-        <LinearLayout android:id="@+id/extra_options_container"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone">
-
-            <!-- PRESENCE -->
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/EditEvent_Layout">
-
+                <!-- MORE OPTIONS -->
+                <!-- PRESENCE -->
                 <TextView android:id="@+id/presence_label"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
@@ -336,17 +224,9 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:entries="@array/availability"
-                    android:prompt="@string/presence_label"
-                    android:contentDescription="@string/presence_label"/>
-            </LinearLayout>
+                    android:prompt="@string/presence_label"/>
 
-            <!-- PRIVACY -->
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/EditEvent_Layout">
-
+                <!-- PRIVACY -->
                 <TextView android:id="@+id/privacy_label"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
@@ -357,90 +237,141 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:entries="@array/visibility"
-                    android:prompt="@string/privacy_label"
-                    android:contentDescription="@string/privacy_label"/>
+                    android:prompt="@string/privacy_label"/>
+
+                <!-- REMINDERS -->
+                <View android:id="@+id/reminders_separator"
+                    android:layout_width="match_parent"
+                    android:layout_height="1dip"
+                    android:background="@android:drawable/divider_horizontal_dark" />
+
+                <TextView android:id="@+id/reminders_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/reminders_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+
+                <LinearLayout android:id="@+id/reminder_items_container"
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical|right"
+                    android:paddingBottom="5dip">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginRight="2dip"
+                        android:text="@string/add_new_reminder"
+                        style="?android:attr/textAppearanceSmall"/>
+
+                    <ImageButton android:id="@+id/reminder_add"
+                        style="@style/PlusButton"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginRight="7dip"
+                        android:gravity="center_vertical|right" />
+
+                </LinearLayout>
+
             </LinearLayout>
-        </LinearLayout>
 
-        <!-- REMINDERS -->
-        <View android:id="@+id/reminders_separator"
-            android:layout_width="match_parent"
-            android:layout_height="1dip"
-            android:background="@android:drawable/divider_horizontal_dark"
-        />
+        </ScrollView>
 
-        <LinearLayout android:id="@+id/reminders_container"
+    <!-- GUESTS/ATTENDEES -->
+        <LinearLayout android:id="@+id/attendees_group"
             android:orientation="vertical"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditEvent_Layout">
+            android:layout_height="match_parent"
+            android:layout_marginLeft="4dip"
+            android:layout_weight="2" >
 
-            <TextView android:id="@+id/reminders_label"
+            <!-- RESPONSE -->
+
+            <TextView android:id="@+id/response_label"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/reminders_label"
+                android:text="@string/view_event_response_label"
                 style="@style/TextAppearance.EditEvent_Label"/>
 
-            <LinearLayout android:id="@+id/reminder_items_container"
-                android:orientation="vertical"
+            <Spinner android:id="@+id/response_value"
+                style="?android:attr/textAppearanceMedium"
+                android:prompt="@string/view_event_response_label"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            </LinearLayout>
-        </LinearLayout>
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical|right"
-            android:paddingBottom="5dip">
+                android:layout_height="wrap_content"
+                android:entries="@array/response_labels1"/>
 
+            <!-- GUEST LIST -->
             <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginRight="2dip"
-                android:text="@string/add_new_reminder"
-                style="?android:attr/textAppearanceSmallInverse"/>
+                android:text="@string/attendees_label"
+                style="@style/TextAppearance.EditEvent_Label"/>
 
-            <ImageButton android:id="@+id/reminder_add"
-                style="@style/PlusButton"
-                android:layout_width="wrap_content"
+            <LinearLayout android:id="@+id/add_guests_container"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <MultiAutoCompleteTextView android:id="@+id/attendees"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:inputType="textEmailAddress|textMultiLine"
+                    android:hint="@string/hint_attendees"
+                    android:layout_marginTop="6dip"
+                    android:layout_marginBottom="6dip"
+                    android:imeOptions="actionNext"
+                    android:layout_weight="1"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+
+                <ImageButton android:id="@+id/attendee_add"
+                    style="@style/PlusButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                />
+
+            </LinearLayout>
+
+            <ListView
+                android:id="@+id/attendee_list"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginRight="7dip"
-                android:gravity="center_vertical|right"
-                android:contentDescription="@string/add_new_reminder"
-            />
+                android:orientation="vertical"
+                android:cacheColorHint="@android:color/background_light"/>
+
         </LinearLayout>
 
-        <!-- BUTTONS -->
-        <LinearLayout
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingTop="5dip"
-            android:paddingLeft="4dip"
-            android:paddingRight="4dip"
-            android:paddingBottom="1dip"
-            android:background="@android:drawable/bottom_bar" >
-
-            <Button android:id="@+id/save"
-                android:layout_width="0dip"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:text="@string/save_label"
-            />
-
-            <Button android:id="@+id/discard"
-                android:layout_width="0dip"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:text="@string/discard_label"
-            />
-
-            <Button android:id="@+id/delete"
-                android:layout_width="0dip"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:text="@string/delete_label"
-            />
-        </LinearLayout>
     </LinearLayout>
-</ScrollView>
+
+    <!-- BUTTONS -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@android:style/ButtonBar" >
+
+        <Button android:id="@+id/save"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/save_label" />
+
+        <Button android:id="@+id/discard"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/discard_label" />
+
+        <Button android:id="@+id/delete"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/delete_label" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/edit_event_fragment.xml b/res/layout/edit_event_fragment.xml
new file mode 100644
index 0000000..3257671
--- /dev/null
+++ b/res/layout/edit_event_fragment.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent" android:layout_height="match_parent"
+    android:id="@+id/edit_event">
+    
+    <FrameLayout android:id="@+id/container_1"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+        
+</FrameLayout>
diff --git a/res/layout/edit_reminder_item.xml b/res/layout/edit_reminder_item.xml
index 7cfab51..a0a076c 100644
--- a/res/layout/edit_reminder_item.xml
+++ b/res/layout/edit_reminder_item.xml
@@ -26,14 +26,12 @@
         android:layout_weight="1"
         android:layout_gravity="center_vertical"
         android:layout_marginRight="2dip"
-        android:entries="@array/reminder_minutes_labels"
-        android:contentDescription="@string/content_description_edit_event_reminders"/>
+        android:entries="@array/reminder_minutes_labels"/>
 
     <ImageButton android:id="@+id/reminder_remove"
         style="@style/MinusButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center_vertical"
-        android:contentDescription="@string/content_description_edit_event_remove_reminder"
     />
 </LinearLayout>
diff --git a/res/layout/email_autocomplete_item.xml b/res/layout/email_autocomplete_item.xml
new file mode 100644
index 0000000..0df089e
--- /dev/null
+++ b/res/layout/email_autocomplete_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="50dip"
+    android:orientation="horizontal"
+    android:layout_centerVertical="true"
+    android:layout_gravity="center_vertical">
+    <TextView android:id="@+id/text1"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:paddingLeft="6dip"
+        android:singleLine="true"
+        android:ellipsize="end"
+        />
+    <TextView android:id="@+id/text2"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/text1"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:paddingLeft="6dip"
+        android:singleLine="true"
+        android:ellipsize="end"
+        />
+</RelativeLayout>
diff --git a/res/layout/email_autocomplete_item_loading.xml b/res/layout/email_autocomplete_item_loading.xml
new file mode 100644
index 0000000..7a56d40
--- /dev/null
+++ b/res/layout/email_autocomplete_item_loading.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="50dip"
+    android:orientation="horizontal"
+    android:layout_centerVertical="true"
+    android:layout_gravity="center_vertical">
+    <TextView android:id="@+id/text1"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:paddingLeft="6dip"
+        android:singleLine="true"
+        android:ellipsize="end"
+        />
+    <ProgressBar android:id="@+id/progress_circular"
+        style="?android:attr/progressBarStyleSmall"
+        android:max="10000"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:layout_marginLeft="6dip"
+        android:layout_marginRight="6dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</RelativeLayout>
diff --git a/res/layout/event_info_activity.xml b/res/layout/event_info_activity.xml
index fc3ee63..f550c2b 100644
--- a/res/layout/event_info_activity.xml
+++ b/res/layout/event_info_activity.xml
@@ -14,254 +14,212 @@
      limitations under the License.
 -->
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+    android:background="#fafafa"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_weight="1.0" >
+    android:layout_height="wrap_content"
+    android:padding="4dip"
+    android:layout_alignParentTop="true">
 
     <LinearLayout
-        android:orientation="vertical"
-        android:background="#fafafa"
+        android:id="@+id/event"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_alignParentTop="true" >
+        android:orientation="horizontal">
 
-        <LinearLayout android:id="@+id/cal_background"
+        <View
+            android:id="@+id/color"
+            android:layout_width="28dip"
+            android:layout_height="28dip"
+            android:layout_gravity="center|left"
+            android:layout_marginRight="10dip" />
+
+        <!-- WHAT -->
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:autoLink="all"
+            android:textStyle="bold"
+            style="?android:attr/textAppearanceLarge" />
+
+    </LinearLayout>
+
+    <!-- WHEN -->
+    <TextView
+        android:id="@+id/when"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="4dip"
+        android:textStyle="bold"
+        style="?android:attr/textAppearanceMedium" />
+
+    <!-- REPEATS -->
+    <LinearLayout
+        android:id="@+id/repeat_container"
+        android:visibility="gone"
+        android:orientation="horizontal"
+        android:paddingTop="4dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <ImageView
+            android:id="@+id/repeat_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_repeat_dark"
+            android:focusable="false"
+            android:clickable="false" />
+
+        <TextView
+            android:id="@+id/repeat"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingTop="10dip"
-            android:paddingBottom="8dip"
-            android:paddingLeft="8dip"
-            android:paddingRight="8dip">
+            android:layout_marginLeft="3dip"
+            style="?android:attr/textAppearanceMedium" />
+    </LinearLayout>
 
-            <LinearLayout android:id="@+id/event"
-                android:background="@drawable/bg_cal_card"
-                android:orientation="vertical"
-                android:layout_height="wrap_content"
-                android:layout_width="match_parent"
-                android:layout_weight="1">
+    <!-- WHERE -->
+    <TextView
+        android:id="@+id/where"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="4dip"
+        android:autoLink="all"
+        style="?android:attr/textAppearanceMedium" />
 
-                <!-- WHAT -->
-                <TextView android:id="@+id/title"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:autoLink="all"
-                    android:textStyle="bold"
-                    android:paddingBottom="7dip"
-                    style="?android:attr/textAppearanceMedium"
-                />
+    <!-- Organizer -->
+    <LinearLayout
+        android:id="@+id/organizer_container"
+        android:visibility="gone"
+        android:orientation="horizontal"
+        android:paddingTop="4dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="5dip">
 
-                <!-- CALENDAR -->
-                <RelativeLayout android:id="@+id/calendar_container"
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingBottom="5dip">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="5dip"
+            android:text="@string/view_event_organizer_label"
+            style="?android:attr/textAppearanceMedium" />
 
-                    <TextView android:id="@+id/calendar_label"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginRight="5dip"
-                        android:text="@string/view_event_calendar_label"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-
-                    <TextView android:id="@+id/calendar"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:singleLine="true"
-                        android:layout_toRightOf="@id/calendar_label"
-                        android:layout_alignTop="@id/calendar_label"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-
-                    <TextView android:id="@+id/owner"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:singleLine="true"
-                        android:layout_alignLeft="@id/calendar_label"
-                        android:layout_below="@id/calendar_label"
-                        android:textAppearance="?android:attr/textAppearanceSmall"
-                    />
-
-                </RelativeLayout>
-
-                <View android:id="@+id/divider"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:background="@android:drawable/divider_horizontal_dark"
-                />
-
-                <!-- WHEN -->
-                <TextView android:id="@+id/when"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:paddingTop="7dip"
-                    android:textStyle="bold"
-                    style="?android:attr/textAppearanceSmall"
-                />
-
-                <!-- TIMEZONE -->
-                <LinearLayout android:id="@+id/timezone_container"
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <TextView android:id="@+id/timezone_label"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginRight="5dip"
-                        android:text="@string/view_event_timezone_label"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-
-                    <TextView android:id="@+id/timezone"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-                </LinearLayout>
-
-                <!-- REPEATS -->
-                <LinearLayout android:id="@+id/repeat_container"
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <ImageView android:id="@+id/repeat_icon"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_gravity="center"
-                        android:src="@drawable/ic_repeat_dark"
-                        android:focusable="false"
-                        android:clickable="false"
-                    />
-
-                    <TextView android:id="@+id/repeat"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:layout_marginLeft="3dip"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-                </LinearLayout>
-
-                <!-- WHERE -->
-                <TextView android:id="@+id/where"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingLeft="8dip"
-                    android:paddingRight="8dip"
-                    android:autoLink="all"
-                    style="?android:attr/textAppearanceSmall"
-                />
-
-                <!-- Organizer -->
-                <LinearLayout android:id="@+id/organizer_container"
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingBottom="5dip"
-                >
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginRight="5dip"
-                        android:text="@string/view_event_organizer_label"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-
-                    <TextView android:id="@+id/organizer"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        style="?android:attr/textAppearanceSmall"
-                    />
-                </LinearLayout>
-
-                <!-- DESCRIPTION -->
-                <TextView android:id="@+id/description"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingLeft="8dip"
-                    android:paddingRight="8dip"
-                    android:autoLink="all"
-                    style="?android:attr/textAppearanceSmall"
-                />
-
-            </LinearLayout>
-        </LinearLayout>
-
-        <!-- RESPONSE -->
-        <LinearLayout android:id="@+id/response_container"
-            android:orientation="vertical"
+        <TextView
+            android:id="@+id/organizer"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingLeft="8dip"
-            android:paddingRight="8dip"
-            android:paddingTop="5dip"
-            android:paddingBottom="1dip">
+            style="?android:attr/textAppearanceMedium" />
+    </LinearLayout>
 
-            <TextView android:id="@+id/response_label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/view_event_response_label"
-                style="@style/TextAppearance.EventInfo_Label"/>
+    <!-- DESCRIPTION -->
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dip"
+        android:paddingBottom="12dip"
+        android:autoLink="all"
+        style="?android:attr/textAppearanceMedium" />
 
-            <Spinner android:id="@+id/response_value"
-                style="?android:attr/textAppearanceMedium"
-                android:prompt="@string/view_event_response_label"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:entries="@array/response_labels1"/>
-        </LinearLayout>
+    <!-- RESPONSE -->
+    <LinearLayout
+        android:id="@+id/response_container"
+        android:visibility="gone"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="4dip">
 
-        <!-- GUEST LIST -->
+        <TextView
+            android:id="@+id/response_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/view_event_response_label"
+            style="@style/TextAppearance.EventInfo_Label" />
+
+        <Spinner
+            android:id="@+id/response_value"
+            style="?android:attr/textAppearanceLarge"
+            android:prompt="@string/view_event_response_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/response_labels1" />
+    </LinearLayout>
+
+    <!-- GUEST LIST -->
+    <TextView
+        android:id="@+id/attendee_list"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="4dip"
+        style="?android:attr/textAppearanceMedium" />
+
+    <!-- REMINDERS -->
+    <LinearLayout
+        android:id="@+id/reminders_container"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="4dip">
+
+        <TextView
+            android:id="@+id/reminders_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/reminders_label"
+            style="@style/TextAppearance.EventInfo_Label" />
+
         <LinearLayout
-            android:id="@+id/attendee_list"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingLeft="8dip"
-            android:orientation="vertical" />
-
-        <!-- REMINDERS -->
-        <LinearLayout android:id="@+id/reminders_container"
+            android:id="@+id/reminder_items_container"
+            style="?android:attr/textAppearanceLarge"
             android:orientation="vertical"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingLeft="8dip"
-            android:paddingRight="7dip"
-            android:paddingTop="5dip"
-            android:paddingBottom="5dip">
+            android:layout_height="wrap_content">
+        </LinearLayout>
 
-            <TextView android:id="@+id/reminders_label"
+        <LinearLayout
+            android:id="@+id/reminder_adder"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical|right">
+
+            <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/reminders_label"
-                style="@style/TextAppearance.EventInfo_Label"/>
+                android:layout_marginRight="7dip"
+                android:text="@string/add_new_reminder" />
 
-            <LinearLayout android:id="@+id/reminder_items_container"
-                style="?android:attr/textAppearanceMedium"
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            </LinearLayout>
-            <LinearLayout android:id="@+id/reminder_adder"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:gravity="center_vertical|right">
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginRight="7dip"
-                    android:text="@string/add_new_reminder"/>
-
-                <ImageButton android:id="@+id/reminder_add"
-                    style="@style/PlusButton"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                />
-            </LinearLayout>
+            <ImageButton
+                android:id="@+id/reminder_add"
+                style="@style/PlusButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
         </LinearLayout>
     </LinearLayout>
-</ScrollView>
+
+    <!-- BUTTONS -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <Button
+            android:id="@+id/edit"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:visibility="gone"
+            android:text="@string/event_edit" />
+
+        <Button
+            android:id="@+id/done"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/save_label" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/mini_calendar_item.xml b/res/layout/mini_calendar_item.xml
new file mode 100644
index 0000000..3792ccf
--- /dev/null
+++ b/res/layout/mini_calendar_item.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:layout_marginRight="6dip"
+    android:layout_marginTop="6dip"
+    android:layout_marginBottom="6dip"
+    android:layout_weight="1">
+
+    <ImageView android:id="@+id/color"
+        android:layout_width="30dip"
+        android:layout_height="30dip"
+        android:layout_marginLeft="6dip"
+        android:layout_marginRight="6dip"
+        android:cropToPadding="true"
+    />
+
+    <TextView android:id="@+id/calendar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/color"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        style="?android:attr/textAppearanceLarge"
+        android:duplicateParentState="true"
+    />
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/calendar"
+        android:layout_alignLeft="@id/calendar"
+        android:ellipsize="marquee"
+        android:maxLines="2"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:duplicateParentState="true"
+        android:visibility="gone"
+    />
+
+</RelativeLayout>
diff --git a/res/layout/month_activity.xml b/res/layout/month_activity.xml
index 4cdd57e..e07ab69 100644
--- a/res/layout/month_activity.xml
+++ b/res/layout/month_activity.xml
@@ -20,9 +20,10 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
 
-    <RelativeLayout
+    <RelativeLayout android:id="@+id/title_bar"
         android:layout_width="match_parent"
         android:layout_height="?android:attr/windowTitleSize"
+        android:visibility="gone"
         style="?android:attr/windowTitleBackgroundStyle">
 
         <TextView android:id="@+id/title"
@@ -67,12 +68,16 @@
             style="@style/MonthView_DayLabel" />
     </LinearLayout>
 
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1.0"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="@*android:drawable/title_bar_shadow">
+        android:foregroundGravity="fill_horizontal|top" >
         <ViewSwitcher android:id="@+id/switcher"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
diff --git a/res/layout/search.xml b/res/layout/search.xml
new file mode 100644
index 0000000..772a765
--- /dev/null
+++ b/res/layout/search.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/event_info"
+
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="500dip" />
+
+    <FrameLayout
+        android:id="@+id/search_results"
+
+        android:layout_toLeftOf="@id/event_info"
+        android:layout_alignParentLeft="true"
+
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values-land/integers.xml b/res/layout/select_calendars_fragment.xml
similarity index 67%
copy from res/values-land/integers.xml
copy to res/layout/select_calendars_fragment.xml
index 9ece108..72912ca 100644
--- a/res/values-land/integers.xml
+++ b/res/layout/select_calendars_fragment.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2010 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.
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 
-<resources>
-    <integer name="number_of_hours">8</integer>
-</resources>
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="0dip"
+    android:clickable="true"
+    android:layout_weight="1" />
diff --git a/res/layout/simple_dropdown_item_1line.xml b/res/layout/simple_dropdown_item_1line.xml
new file mode 100644
index 0000000..4b72b01
--- /dev/null
+++ b/res/layout/simple_dropdown_item_1line.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml
+**
+** Copyright 2010, 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    style="?android:attr/dropDownItemStyle"
+    android:textAppearance="?android:attr/textAppearanceLarge"
+    android:singleLine="true"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:ellipsize="marquee" />
diff --git a/res/layout/timezone_footer.xml b/res/layout/timezone_footer.xml
index f01d6ce..05c017f 100644
--- a/res/layout/timezone_footer.xml
+++ b/res/layout/timezone_footer.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+ <!-- Copyright (C) 2010 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
+      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.
+      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.
 -->
 
 <TextView
@@ -24,5 +24,4 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingLeft="10dip"
     android:paddingRight="10dip"
-    android:text="@string/edit_event_show_all"
-    style="?android:attr/textAppearanceMediumInverse" />
+    style="?android:attr/textAppearanceMediumInverse" />
\ No newline at end of file
diff --git a/res/layout/week_activity.xml b/res/layout/week_activity.xml
deleted file mode 100644
index 967da8e..0000000
--- a/res/layout/week_activity.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="?android:attr/windowTitleSize"
-        style="?android:attr/windowTitleBackgroundStyle">
-
-        <TextView android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            style="?android:attr/windowTitleStyle"
-            android:background="@null"
-            android:fadingEdge="horizontal"
-        />
-
-        <ProgressBar android:id="@+id/progress_circular"
-            style="?android:attr/progressBarStyleSmall"
-            android:visibility="gone"
-            android:max="10000"
-            android:layout_centerVertical="true"
-            android:layout_alignParentRight="true"
-            android:layout_marginLeft="6dip"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </RelativeLayout>
-
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1.0"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="@*android:drawable/title_bar_shadow">
-        <ViewSwitcher android:id="@+id/switcher"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-    </FrameLayout>
-</LinearLayout>
diff --git a/res/menu/all_in_one_title_bar.xml b/res/menu/all_in_one_title_bar.xml
new file mode 100644
index 0000000..d8c36f2
--- /dev/null
+++ b/res/menu/all_in_one_title_bar.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/action_search"
+        android:alphabeticShortcut="f"
+        android:title="@string/search"
+        android:icon="@android:drawable/ic_menu_search"
+        android:showAsAction="ifRoom"
+        android:actionViewClass="android.widget.SearchView" />
+    <item
+        android:id="@+id/action_day"
+        android:alphabeticShortcut="d"
+        android:title="@string/day_view"
+        android:icon="@android:drawable/ic_menu_day"
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/action_week"
+        android:alphabeticShortcut="w"
+        android:title="@string/week_view"
+        android:icon="@android:drawable/ic_menu_week"
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/action_month"
+        android:alphabeticShortcut="m"
+        android:title="@string/month_view"
+        android:icon="@android:drawable/ic_menu_month"
+        android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/action_today"
+        android:alphabeticShortcut="t"
+        android:title="@string/goto_today"
+        android:icon="@android:drawable/ic_menu_today"
+        android:showAsAction="always" />
+    <item
+        android:id="@+id/action_create_event"
+        android:alphabeticShortcut="n"
+        android:title="@string/event_create"
+        android:icon="@android:drawable/ic_menu_add"
+        android:showAsAction="always" />
+    <item
+        android:id="@+id/action_refresh"
+        android:alphabeticShortcut="r"
+        android:title="@string/calendar_refresh"
+        android:icon="@drawable/ic_menu_refresh" />
+    <item
+        android:id="@+id/action_settings"
+        android:alphabeticShortcut="s"
+        android:title="@string/menu_preferences"
+        android:icon="@android:drawable/ic_menu_preferences" />
+</menu>
diff --git a/res/menu/search_title_bar.xml b/res/menu/search_title_bar.xml
new file mode 100644
index 0000000..1cc4d35
--- /dev/null
+++ b/res/menu/search_title_bar.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<menu
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/action_search"
+        android:alphabeticShortcut="f"
+        android:title="@string/search"
+        android:icon="@android:drawable/ic_menu_search"
+        android:showAsAction="ifRoom"
+        android:actionViewClass="android.widget.SearchView" />
+    <item
+        android:id="@+id/action_today"
+        android:alphabeticShortcut="t"
+        android:title="@string/goto_today"
+        android:icon="@android:drawable/ic_menu_today"
+        android:showAsAction="always" />
+    <item
+        android:id="@+id/action_settings"
+        android:alphabeticShortcut="s"
+        android:title="@string/menu_preferences"
+        android:icon="@android:drawable/ic_menu_preferences" />
+</menu>
diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml
index 057bf21..e39cbc7 100644
--- a/res/values-cs/arrays.xml
+++ b/res/values-cs/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dny"</item>
     <item msgid="3390146235648970198">"1 týden"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Žádné"</item>
+    <item msgid="3377199512833209480">"1 minuta"</item>
+    <item msgid="5023205715881387516">"5 minut"</item>
+    <item msgid="7720870540593319107">"10 minut"</item>
+    <item msgid="1653841700859180907">"15 minut"</item>
+    <item msgid="3741629746313107482">"20 minut"</item>
+    <item msgid="5357458446224446398">"25 minut"</item>
+    <item msgid="403143749684280703">"30 minut"</item>
+    <item msgid="5611574008997362948">"45 minut"</item>
+    <item msgid="30462793717552">"1 hodina"</item>
+    <item msgid="119808229352802480">"2 hodiny"</item>
+    <item msgid="7456742278252094745">"3 hodiny"</item>
+    <item msgid="5263928189935026559">"12 hodin"</item>
+    <item msgid="6451763179435252852">"24 hodin"</item>
+    <item msgid="966666856467721784">"2 dny"</item>
+    <item msgid="5840973497558794742">"1 týden"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Výchozí hodnota národního prostředí"</item>
+    <item msgid="134027225275475280">"Sobota"</item>
+    <item msgid="95029346069903091">"Neděle"</item>
+    <item msgid="5840983116375063739">"Pondělí"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshallovy ostrovy"</item>
     <item msgid="7176569939440505575">"Midwayské ostrovy"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Bělehrad"</item>
     <item msgid="3284290759549286849">"Brusel"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paříž"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Západoafrický čas"</item>
-    <item msgid="7527935722014187591">"Ammán, Jordánsko"</item>
-    <item msgid="672509109295227133">"Atény, Istanbul"</item>
-    <item msgid="1835470340349221646">"Bejrút, Libanon"</item>
-    <item msgid="9006223109403693650">"Káhira"</item>
-    <item msgid="2877875673636361825">"Helsinky"</item>
-    <item msgid="4528124308590807049">"Jeruzalém"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdád"</item>
-    <item msgid="8077211460729251609">"Moskva"</item>
-    <item msgid="6103383184232804522">"Kuvajt"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teherán"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Jerevan"</item>
-    <item msgid="2537944335416132075">"Dubaj"</item>
-    <item msgid="5630432498109110949">"Kábul"</item>
-    <item msgid="2274762525650993846">"Islámábád, Karáčí"</item>
-    <item msgid="272153171103339242">"Uralsk"</item>
-    <item msgid="4757644551253886090">"Jekatěrinburg"</item>
-    <item msgid="3036512479614493957">"Kalkata"</item>
-    <item msgid="4190271241277312277">"Srí Lanka"</item>
-    <item msgid="4489139826358657866">"Káthmandú"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Rangún"</item>
-    <item msgid="3838899720161825998">"Krasnojarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Peking"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Tchaj-pej"</item>
-    <item msgid="3043691515163887690">"Soul"</item>
-    <item msgid="6369124969606372122">"Tokio, Ósaka"</item>
-    <item msgid="8317842783685169193">"Jakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fidži"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Žádné"</item>
-    <item msgid="3377199512833209480">"1 minuta"</item>
-    <item msgid="5023205715881387516">"5 minut"</item>
-    <item msgid="7720870540593319107">"10 minut"</item>
-    <item msgid="1653841700859180907">"15 minut"</item>
-    <item msgid="3741629746313107482">"20 minut"</item>
-    <item msgid="5357458446224446398">"25 minut"</item>
-    <item msgid="403143749684280703">"30 minut"</item>
-    <item msgid="5611574008997362948">"45 minut"</item>
-    <item msgid="30462793717552">"1 hodina"</item>
-    <item msgid="119808229352802480">"2 hodiny"</item>
-    <item msgid="7456742278252094745">"3 hodiny"</item>
-    <item msgid="5263928189935026559">"12 hodin"</item>
-    <item msgid="6451763179435252852">"24 hodin"</item>
-    <item msgid="966666856467721784">"2 dny"</item>
-    <item msgid="5840973497558794742">"1 týden"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Upozornění"</item>
-    <item msgid="8723674378160518790">"Oznámení na stavovém řádku"</item>
-    <item msgid="8433736231022186661">"Vypnuto"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Západoafrický čas"</item>
+    <item msgid="1358916275855835758">"Ammán, Jordánsko"</item>
+    <item msgid="5900185968201011015">"Atény, Istanbul"</item>
+    <item msgid="4975304496845689895">"Bejrút, Libanon"</item>
+    <item msgid="4099790960681254059">"Káhira"</item>
+    <item msgid="4174072690298729061">"Helsinky"</item>
+    <item msgid="5233437715028921599">"Jeruzalém"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdád"</item>
+    <item msgid="3703352987270492256">"Moskva"</item>
+    <item msgid="1657996636394546903">"Kuvajt"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teherán"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Jerevan"</item>
+    <item msgid="5094507376721687886">"Dubaj"</item>
+    <item msgid="2849654571454095226">"Kábul"</item>
+    <item msgid="3641915655045056824">"Islámábád, Karáčí"</item>
+    <item msgid="5795351343828254400">"Uralsk"</item>
+    <item msgid="8779467771115356898">"Jekatěrinburg"</item>
+    <item msgid="297412491658718331">"Kalkata"</item>
+    <item msgid="2290076787008042202">"Srí Lanka"</item>
+    <item msgid="4119703758417825749">"Káthmandú"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Rangún"</item>
+    <item msgid="6275146302340764856">"Krasnojarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Peking"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Tchaj-pej"</item>
+    <item msgid="4972387553080120086">"Soul"</item>
+    <item msgid="5749349714875345358">"Tokio, Ósaka"</item>
+    <item msgid="4435870687083770252">"Jakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fidži"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Zaneprázdněný"</item>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 391420c..110718e 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalendář"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Kalendář – vše v jednom"</string>
     <string name="what_label" msgid="1933578391985810049">"Co"</string>
     <string name="where_label" msgid="3472482206065595118">"Kde"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Časové pásmo"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Časové pásmo"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Hosté"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Opakování"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 den"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dnů"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Aktualizovat"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Zobrazit agendu"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Zobrazit den"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Dnes"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalendáře"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Nastavení"</string>
+    <string name="search" msgid="550656004486017426">"Hledat"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(plus o <xliff:g id="MORE_COUNT">%d</xliff:g> více …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalendáře"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synchronizováno, viditelné"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synchronizováno, skryté"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"nesynchronizováno, skryté"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Podrobnosti události"</string>
     <string name="hint_what" msgid="709155115005044531">"Název události"</string>
     <string name="hint_where" msgid="3116239630502213241">"Místo události"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalendář"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Zobrazit další možnosti"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Skrýt další možnosti"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Zobrazit vše &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Zobrazit vše"</string>
     <string name="description_label" msgid="7193475046717505855">"Popis"</string>
     <string name="presence_label" msgid="6169724148441175862">"Zobrazit mě jako"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Soukromá/Veřejná"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Připomenutí"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Žádné kalendáře"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Nemáte žádné kalendáře."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Od data"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Od času"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Do data"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Do času"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Připomenutí"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Odebrání připomenutí"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalendář:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizátor:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Místní časové pásmo:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Zúčastníte se?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Dnes, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Načítání..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Zobrazují se události od <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Další položky zobrazíte klepnutím."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Zobrazují se události do <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Další položky zobrazíte klepnutím."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Zobrazeny události od <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Dotykem můžete vyhledat dřívější události."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Zobrazeny události do <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Dotykem můžete vyhledat pozdější události."</string>
+    <string name="search_title" msgid="8898015280250716254">"Hledat v mých kalendářích"</string>
     <string name="num_events" msgid="3351672964607162257">"Počet událostí"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Upravit událost"</string>
     <string name="delete_label" msgid="874742811918030739">"Smazat"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Smazat událost"</string>
     <string name="save_label" msgid="2133599104834432589">"Hotovo"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Vrátit zpět"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Zrušit"</string>
     <string name="import_label" msgid="6993796574741477222">"Importovat"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Odložit vše"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Zavřít vše"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Tato událost bude smazána."</string>
     <string name="delete_title" msgid="5143743507524995383">"Smazat"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Změnit odpověď"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Nastavení"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Nastavení zobrazení kalendáře"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Nastavení připomenutí"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Nastavení připomenutí"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Skrýt odmítnuté události"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Nastavit upozornění a oznámení"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Upozornění a oznámení"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Začátek týdne"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Začátek týdne"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Vymazat historii vyhledávání"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Odebrat všechna provedená vyhledávání"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Historie vyhledávání vymazána"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Oznámení"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrace"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrovat také u připomenutí"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Vybrat vyzvánění"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Vyskakovací oznámení"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Výchozí čas připomenutí"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Výchozí čas připomenutí"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nikdy"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrace"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalendář"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"Jedna další událost"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> dalších událostí"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Žádné nadcházející události"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Zítra"</string>
+    <string name="in_progress" msgid="3637799356103489011">"probíhá"</string>
+    <string name="today" msgid="8577375020252453006">"Dnes"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Vyhledávání na webu <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-da/arrays.xml b/res/values-da/arrays.xml
index 094a2ac..d16ea0f 100644
--- a/res/values-da/arrays.xml
+++ b/res/values-da/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dage"</item>
     <item msgid="3390146235648970198">"1 uge"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Ingen"</item>
+    <item msgid="3377199512833209480">"1 minut"</item>
+    <item msgid="5023205715881387516">"5 minutter"</item>
+    <item msgid="7720870540593319107">"10 minutter"</item>
+    <item msgid="1653841700859180907">"15 minutter"</item>
+    <item msgid="3741629746313107482">"20 minutter"</item>
+    <item msgid="5357458446224446398">"25 minutter"</item>
+    <item msgid="403143749684280703">"30 minutter"</item>
+    <item msgid="5611574008997362948">"45 minutter"</item>
+    <item msgid="30462793717552">"1 time"</item>
+    <item msgid="119808229352802480">"2 timer"</item>
+    <item msgid="7456742278252094745">"3 timer"</item>
+    <item msgid="5263928189935026559">"12 timer"</item>
+    <item msgid="6451763179435252852">"24 timer"</item>
+    <item msgid="966666856467721784">"2 dage"</item>
+    <item msgid="5840973497558794742">"1 uge"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Landestandard"</item>
+    <item msgid="134027225275475280">"lørdag"</item>
+    <item msgid="95029346069903091">"søndag"</item>
+    <item msgid="5840983116375063739">"mandag"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshalløerne"</item>
     <item msgid="7176569939440505575">"Midwayøen"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Beograd"</item>
     <item msgid="3284290759549286849">"Bruxelles"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Vestafrika"</item>
-    <item msgid="7527935722014187591">"Amman, Jordan"</item>
-    <item msgid="672509109295227133">"Athen, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beirut, Libanon"</item>
-    <item msgid="9006223109403693650">"Kairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerusalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskva"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Yerevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Yekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Beijing"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seoul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Ingen"</item>
-    <item msgid="3377199512833209480">"1 minut"</item>
-    <item msgid="5023205715881387516">"5 minutter"</item>
-    <item msgid="7720870540593319107">"10 minutter"</item>
-    <item msgid="1653841700859180907">"15 minutter"</item>
-    <item msgid="3741629746313107482">"20 minutter"</item>
-    <item msgid="5357458446224446398">"25 minutter"</item>
-    <item msgid="403143749684280703">"30 minutter"</item>
-    <item msgid="5611574008997362948">"45 minutter"</item>
-    <item msgid="30462793717552">"1 time"</item>
-    <item msgid="119808229352802480">"2 timer"</item>
-    <item msgid="7456742278252094745">"3 timer"</item>
-    <item msgid="5263928189935026559">"12 timer"</item>
-    <item msgid="6451763179435252852">"24 timer"</item>
-    <item msgid="966666856467721784">"2 dage"</item>
-    <item msgid="5840973497558794742">"1 uge"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Underretning"</item>
-    <item msgid="8723674378160518790">"Meddelelse i statusbjælken"</item>
-    <item msgid="8433736231022186661">"Fra"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Vestafrika"</item>
+    <item msgid="1358916275855835758">"Amman, Jordan"</item>
+    <item msgid="5900185968201011015">"Athen, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beirut, Libanon"</item>
+    <item msgid="4099790960681254059">"Kairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerusalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskva"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Yerevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Yekaterinburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Beijing"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seoul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Optaget"</item>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 280edb7..24c9278 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalender"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Kalender AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Hvad"</string>
     <string name="where_label" msgid="3472482206065595118">"Hvor"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Tidszone"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Tidszone"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Gæster"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Gentagelse"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dag"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dage"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Opdater"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Vis agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Vis dag"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"I dag"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalendere"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Indstillinger"</string>
+    <string name="search" msgid="550656004486017426">"Søg"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(plus <xliff:g id="MORE_COUNT">%d</xliff:g> flere ...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalendere"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synkroniseret, synlig"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synkroniseret, ikke synlig"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"ikke synkroniseret, ikke synlig"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Detaljer om begivenhed"</string>
     <string name="hint_what" msgid="709155115005044531">"Begivenhedens navn"</string>
     <string name="hint_where" msgid="3116239630502213241">"Begivenhedens placering"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalender"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Vis flere indstillinger"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Skjul flere indstillinger"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Vis alle &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Vis alle"</string>
     <string name="description_label" msgid="7193475046717505855">"Beskrivelse"</string>
     <string name="presence_label" msgid="6169724148441175862">"Vis mig som"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Fortrolighed"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Påmindelser"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Der er ingen kalendere"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Du har ingen kalendere."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Fra dato"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Fra klokkeslæt"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Til dato"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Til klokkeslæt"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Påmindelser"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Fjern påmindelse"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalender:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organisator:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokal tidszone:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Deltager?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"I dag <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Indlæser ..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Viser begivenheder siden <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tryk for at lede efter flere."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Viser begivenheder indtil <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Tryk for at lede efter flere."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Viser begivenheder siden <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tryk for at søge efter tidligere begivenheder."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Viser begivenheder indtil <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Tryk for at søge efter senere begivenheder."</string>
+    <string name="search_title" msgid="8898015280250716254">"Søg i mine kalendere"</string>
     <string name="num_events" msgid="3351672964607162257">"Ant. beg."</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Rediger begivenhed"</string>
     <string name="delete_label" msgid="874742811918030739">"Slet"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Slet begivenhed"</string>
     <string name="save_label" msgid="2133599104834432589">"Udfør"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Annuller"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Annuller"</string>
     <string name="import_label" msgid="6993796574741477222">"Importer"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Sæt alle i slumretilstand"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Annuller alle"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Denne begivenhed slettes."</string>
     <string name="delete_title" msgid="5143743507524995383">"Slet"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Skift svar"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Indstillinger"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Indstillinger for kalendervisning"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Indstillinger for påmindelser"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Indstillinger for påmindelser"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Skjul afviste begivenhed."</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Angiv underret./medd."</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Underretninger og meddelelser"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Ugen starter"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Ugen starter"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Ryd søgeoversigt"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Fjern alle foretagne søgninger"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Søgeoversigten er ryddet"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Underretninger"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibration"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrer også ved påmindelser"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Vælg ringetone"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Popupunderretning"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Std.tid for påmindelser"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Std.tid for påmindelser"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -141,11 +159,21 @@
     <string name="preferences_use_home_tz_descrip" msgid="4835375381297413746">"Viser kalenderbegivenheder i din tidszone, når du rejser"</string>
     <string name="preferences_home_tz_title" msgid="6680000444088818578">"Egen tidszone"</string>
     <string name="preferences_about_title" msgid="2810624892412097879">"Om"</string>
-    <string name="preferences_build_version" msgid="8242471883413502068">"Version"</string>
+    <string name="preferences_build_version" msgid="8242471883413502068">"Version "</string>
   <string-array name="prefEntries_alerts_vibrateWhen">
     <item msgid="2539376794936035639">"Altid"</item>
     <item msgid="8091719131860840185">"Kun ved lydløs"</item>
     <item msgid="4552962311897985633">"Aldrig"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibration"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalender"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"1 begivenhed mere"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> begivenheder mere"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Ingen kommende kalenderbegivenheder"</string>
+    <string name="tomorrow" msgid="614434811813859237">"I morgen"</string>
+    <string name="in_progress" msgid="3637799356103489011">"i gang"</string>
+    <string name="today" msgid="8577375020252453006">"I dag"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Søger efter <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
index 929f74e..5c8c1c3 100644
--- a/res/values-de/arrays.xml
+++ b/res/values-de/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 Tage"</item>
     <item msgid="3390146235648970198">"1 Woche"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Keine"</item>
+    <item msgid="3377199512833209480">"1 Minute"</item>
+    <item msgid="5023205715881387516">"5 Minuten"</item>
+    <item msgid="7720870540593319107">"10 Minuten"</item>
+    <item msgid="1653841700859180907">"15 Minuten"</item>
+    <item msgid="3741629746313107482">"20 Minuten"</item>
+    <item msgid="5357458446224446398">"25 Minuten"</item>
+    <item msgid="403143749684280703">"30 Minuten"</item>
+    <item msgid="5611574008997362948">"45 Minuten"</item>
+    <item msgid="30462793717552">"1 Stunde"</item>
+    <item msgid="119808229352802480">"2 Stunden"</item>
+    <item msgid="7456742278252094745">"3 Stunden"</item>
+    <item msgid="5263928189935026559">"12 Stunden"</item>
+    <item msgid="6451763179435252852">"24 Stunden"</item>
+    <item msgid="966666856467721784">"2 Tage"</item>
+    <item msgid="5840973497558794742">"1 Woche"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Gebietsschema - Standard"</item>
+    <item msgid="134027225275475280">"Samstag"</item>
+    <item msgid="95029346069903091">"Sonntag"</item>
+    <item msgid="5840983116375063739">"Montag"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshallinseln"</item>
     <item msgid="7176569939440505575">"Midwayinseln"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrad"</item>
     <item msgid="3284290759549286849">"Brüssel"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"W. Africa Time"</item>
-    <item msgid="7527935722014187591">"Amman, Jordanien"</item>
-    <item msgid="672509109295227133">"Athen, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beirut, Libanon"</item>
-    <item msgid="9006223109403693650">"Kairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerusalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskau"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tiflis"</item>
-    <item msgid="3344801680659888795">"Eriwan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karatschi"</item>
-    <item msgid="272153171103339242">"Oral"</item>
-    <item msgid="4757644551253886090">"Jekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kalkutta"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Kathmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Rangun"</item>
-    <item msgid="3838899720161825998">"Krasnojarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Peking"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipeh"</item>
-    <item msgid="3043691515163887690">"Seoul"</item>
-    <item msgid="6369124969606372122">"Tokio, Osaka"</item>
-    <item msgid="8317842783685169193">"Jakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Wladiwostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fidschi"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Keine"</item>
-    <item msgid="3377199512833209480">"1 Minute"</item>
-    <item msgid="5023205715881387516">"5 Minuten"</item>
-    <item msgid="7720870540593319107">"10 Minuten"</item>
-    <item msgid="1653841700859180907">"15 Minuten"</item>
-    <item msgid="3741629746313107482">"20 Minuten"</item>
-    <item msgid="5357458446224446398">"25 Minuten"</item>
-    <item msgid="403143749684280703">"30 Minuten"</item>
-    <item msgid="5611574008997362948">"45 Minuten"</item>
-    <item msgid="30462793717552">"1 Stunde"</item>
-    <item msgid="119808229352802480">"2 Stunden"</item>
-    <item msgid="7456742278252094745">"3 Stunden"</item>
-    <item msgid="5263928189935026559">"12 Stunden"</item>
-    <item msgid="6451763179435252852">"24 Stunden"</item>
-    <item msgid="966666856467721784">"2 Tage"</item>
-    <item msgid="5840973497558794742">"1 Woche"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Benachrichtigung"</item>
-    <item msgid="8723674378160518790">"Statuszeilennachricht"</item>
-    <item msgid="8433736231022186661">"Aus"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"W. Africa Time"</item>
+    <item msgid="1358916275855835758">"Amman, Jordanien"</item>
+    <item msgid="5900185968201011015">"Athen, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beirut, Libanon"</item>
+    <item msgid="4099790960681254059">"Kairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerusalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskau"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tiflis"</item>
+    <item msgid="1943529325618264999">"Eriwan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karatschi"</item>
+    <item msgid="5795351343828254400">"Oral"</item>
+    <item msgid="8779467771115356898">"Jekaterinburg"</item>
+    <item msgid="297412491658718331">"Kalkutta"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Rangun"</item>
+    <item msgid="6275146302340764856">"Krasnojarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Peking"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipeh"</item>
+    <item msgid="4972387553080120086">"Seoul"</item>
+    <item msgid="5749349714875345358">"Tokio, Osaka"</item>
+    <item msgid="4435870687083770252">"Jakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Wladiwostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fidschi"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Beschäftigt"</item>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 0dac8d1..1606f95 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalender"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"AllInOne-Kalender"</string>
     <string name="what_label" msgid="1933578391985810049">"Was"</string>
     <string name="where_label" msgid="3472482206065595118">"Wo"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Zeitzone"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Zeitzone"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Gäste"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Wiederholung"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 Tag"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> Tag(e)"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Aktualis."</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Terminübersicht anzeigen"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Tag anzeigen"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Terminübersicht"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Heute"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalender"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Einstellungen"</string>
+    <string name="search" msgid="550656004486017426">"Suche"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(und <xliff:g id="MORE_COUNT">%d</xliff:g> weitere…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalender"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synchronisiert, sichtbar"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synchronisiert, nicht sichtbar"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"nicht synchronisiert, nicht sichtbar"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Termindetails"</string>
     <string name="hint_what" msgid="709155115005044531">"Terminname"</string>
     <string name="hint_where" msgid="3116239630502213241">"Ort des Termins"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalender"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Zusätzliche Optionen anzeigen"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Zusätzliche Optionen ausblenden"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Alle anzeigen &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Alle anzeigen"</string>
     <string name="description_label" msgid="7193475046717505855">"Beschreibung"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mich anzeigen als"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Datenschutz"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Erinnerungen"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Keine Kalender"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Sie haben keine Kalender."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Anfangsdatum"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Anfangszeit"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Enddatum"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Endzeit"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Erinnerungen"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Erinnerung entfernen"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalender:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizer:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokale Zeitzone:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Teilnahme?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Heute, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Wird geladen..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Termine seit <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> werden angezeigt. Tippen Sie, um die Suche zu erweitern."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Termine bis <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g> werden angezeigt. Tippen Sie, um die Suche zu erweitern."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Termine seit <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> angezeigt. Zum Suchen nach späteren Terminen berühren"</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Termine bis <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g> angezeigt. Zum Suchen nach späteren Terminen berühren"</string>
+    <string name="search_title" msgid="8898015280250716254">"Meine Kalender durchsuchen"</string>
     <string name="num_events" msgid="3351672964607162257">"Anzahl Termine"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Termin bearbeiten"</string>
     <string name="delete_label" msgid="874742811918030739">"Löschen"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Termin löschen"</string>
     <string name="save_label" msgid="2133599104834432589">"Fertig"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Rückgängig"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Abbrechen"</string>
     <string name="import_label" msgid="6993796574741477222">"Import"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Später erinnern"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Alle schließen"</string>
@@ -125,21 +134,30 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Dieser Termin wird gelöscht."</string>
     <string name="delete_title" msgid="5143743507524995383">"Löschen"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Antwort ändern"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Einstellungen"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Einstellung für Kalenderansicht"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Erinnerungseinstellungen"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Erinnerungseinstellungen"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Abgel. Termine ausbl."</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Benachr. &amp; Hinweise"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Benachrichtigungen &amp; Hinweise"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Woche beginnt am"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Woche beginnt am"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Suchverlauf löschen"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Alle Suchvorgänge aus dem Verlauf entfernen"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Suchverlauf gelöscht"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Benachrichtigungen"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibration"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Bei Erinnerungen zusätzlich vibrieren"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Klingelton auswählen"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Pop-up-Benachrichtigung"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Standard-Erinnerungszeit"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Standard-Erinnerungszeit"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
-    <string name="preferences_use_home_tz_title" msgid="5597546849968598210">"Heimatzeitzone beibehalten"</string>
-    <string name="preferences_use_home_tz_descrip" msgid="4835375381297413746">"Zeigt unterwegs Kalender-/Terminzeiten in Heimatzeitzone"</string>
-    <string name="preferences_home_tz_title" msgid="6680000444088818578">"Heimatzeitzone"</string>
+    <string name="preferences_use_home_tz_title" msgid="5597546849968598210">"Benutzerzeitzone"</string>
+    <string name="preferences_use_home_tz_descrip" msgid="4835375381297413746">"Zeigt unterwegs Kalender-/Terminzeiten in Benutzerzeitzone"</string>
+    <string name="preferences_home_tz_title" msgid="6680000444088818578">"Benutzerzeitzone"</string>
     <string name="preferences_about_title" msgid="2810624892412097879">"Info"</string>
     <string name="preferences_build_version" msgid="8242471883413502068">"Build-Version"</string>
   <string-array name="prefEntries_alerts_vibrateWhen">
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nie"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibration"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalender"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"1 weiterer Termin"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> weitere Termine"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Keine anstehenden Kalendertermine"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Morgen"</string>
+    <string name="in_progress" msgid="3637799356103489011">"Vorgang läuft"</string>
+    <string name="today" msgid="8577375020252453006">"Heute"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"<xliff:g id="DOMAIN">%s</xliff:g> wird durchsucht…"</string>
 </resources>
diff --git a/res/values-el/arrays.xml b/res/values-el/arrays.xml
index c7d8fd0..e8c07a5 100644
--- a/res/values-el/arrays.xml
+++ b/res/values-el/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 ημέρες"</item>
     <item msgid="3390146235648970198">"1 εβδομάδα"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Κανένα"</item>
+    <item msgid="3377199512833209480">"1 λεπτό"</item>
+    <item msgid="5023205715881387516">"5 λεπτά"</item>
+    <item msgid="7720870540593319107">"10 λεπτά"</item>
+    <item msgid="1653841700859180907">"15 λεπτά"</item>
+    <item msgid="3741629746313107482">"20 λεπτά"</item>
+    <item msgid="5357458446224446398">"25 λεπτά"</item>
+    <item msgid="403143749684280703">"30 λεπτά"</item>
+    <item msgid="5611574008997362948">"45 λεπτά"</item>
+    <item msgid="30462793717552">"1 ώρα"</item>
+    <item msgid="119808229352802480">"2 ώρες"</item>
+    <item msgid="7456742278252094745">"3 ώρες"</item>
+    <item msgid="5263928189935026559">"12 ώρες"</item>
+    <item msgid="6451763179435252852">"24 ώρες"</item>
+    <item msgid="966666856467721784">"2 ημέρες"</item>
+    <item msgid="5840973497558794742">"1 εβδομάδα"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Προεπιλογή τοπικών ρυθμίσεων"</item>
+    <item msgid="134027225275475280">"Σάββατο"</item>
+    <item msgid="95029346069903091">"Κυριακή"</item>
+    <item msgid="5840983116375063739">"Δευτέρα"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Νήσοι Μάρσαλ"</item>
     <item msgid="7176569939440505575">"Νήσος Μιντγουέι"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Βελιγράδι"</item>
     <item msgid="3284290759549286849">"Βρυξέλλες"</item>
     <item msgid="3112943938015033049">"Σαράγεβο"</item>
-    <item msgid="2184425710639965762">"Παρίσι"</item>
-    <item msgid="5997630882187580225">"Βίντχουκ"</item>
-    <item msgid="3828800493609994208">"Ώρα Δ. Αφρικής"</item>
-    <item msgid="7527935722014187591">"Αμμάν, Ιορδανία"</item>
-    <item msgid="672509109295227133">"Αθήνα, Κωνσταντ."</item>
-    <item msgid="1835470340349221646">"Βηρυτός, Λίβανος"</item>
-    <item msgid="9006223109403693650">"Κάιρο"</item>
-    <item msgid="2877875673636361825">"Ελσίνκι"</item>
-    <item msgid="4528124308590807049">"Ιερουσαλήμ"</item>
-    <item msgid="7823856457885290195">"Μινσκ"</item>
-    <item msgid="8650995074282357197">"Χαράρε"</item>
-    <item msgid="209523815326871285">"Βαγδάτη"</item>
-    <item msgid="8077211460729251609">"Μόσχα"</item>
-    <item msgid="6103383184232804522">"Κουβέιτ"</item>
-    <item msgid="8581935265080466099">"Ναϊρόμπι"</item>
-    <item msgid="1531618843539764728">"Τεχεράνη"</item>
-    <item msgid="141548505188106312">"Μπακού"</item>
-    <item msgid="5974315340250149692">"Τιφλίδα"</item>
-    <item msgid="3344801680659888795">"Γερεβάν"</item>
-    <item msgid="2537944335416132075">"Ντουμπάι"</item>
-    <item msgid="5630432498109110949">"Καμπούλ"</item>
-    <item msgid="2274762525650993846">"Ισλ., Καράτσι"</item>
-    <item msgid="272153171103339242">"Ουράλσκ"</item>
-    <item msgid="4757644551253886090">"Εκατερίνμπουργκ"</item>
-    <item msgid="3036512479614493957">"Καλκούτα"</item>
-    <item msgid="4190271241277312277">"Σρι Λάνκα"</item>
-    <item msgid="4489139826358657866">"Κατμαντού"</item>
-    <item msgid="1195461607876034232">"Αστάνα"</item>
-    <item msgid="8777509327730950228">"Γιανγκόν"</item>
-    <item msgid="3838899720161825998">"Κρασνογιάρσκ"</item>
-    <item msgid="6495532062164690437">"Μπανγκόκ"</item>
-    <item msgid="4910549870048384487">"Πεκίνο"</item>
-    <item msgid="7174954011332328153">"Χονγκ Κονγκ"</item>
-    <item msgid="415034182139280334">"Ιρκούτσκ"</item>
-    <item msgid="3407263784503834649">"Κουάλα Λουμπούρ"</item>
-    <item msgid="6547945295249807755">"Περθ"</item>
-    <item msgid="4571776847411430193">"Ταϊπέι"</item>
-    <item msgid="3043691515163887690">"Σεούλ"</item>
-    <item msgid="6369124969606372122">"Τόκιο, Οσάκα"</item>
-    <item msgid="8317842783685169193">"Γιακούτσκ"</item>
-    <item msgid="6993206457323990990">"Αδελαΐδα"</item>
-    <item msgid="2431970257749452312">"Ντάργουιν"</item>
-    <item msgid="2709489188606506801">"Μπρισμπέιν"</item>
-    <item msgid="6832511138206913044">"Χόμπαρτ"</item>
-    <item msgid="6933429790996727761">"Σύδνεϋ, Καμπέρα"</item>
-    <item msgid="4546625974877459074">"Βλαδιβοστόκ"</item>
-    <item msgid="7421491250467489614">"Γκουάμ"</item>
-    <item msgid="3450367738093440984">"Μάγκανταν"</item>
-    <item msgid="5046540868552082971">"Όκλαντ"</item>
-    <item msgid="481903191189494072">"Φίτζι"</item>
-    <item msgid="1926932394586102998">"Τόνγκα"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Κανένα"</item>
-    <item msgid="3377199512833209480">"1 λεπτό"</item>
-    <item msgid="5023205715881387516">"5 λεπτά"</item>
-    <item msgid="7720870540593319107">"10 λεπτά"</item>
-    <item msgid="1653841700859180907">"15 λεπτά"</item>
-    <item msgid="3741629746313107482">"20 λεπτά"</item>
-    <item msgid="5357458446224446398">"25 λεπτά"</item>
-    <item msgid="403143749684280703">"30 λεπτά"</item>
-    <item msgid="5611574008997362948">"45 λεπτά"</item>
-    <item msgid="30462793717552">"1 ώρα"</item>
-    <item msgid="119808229352802480">"2 ώρες"</item>
-    <item msgid="7456742278252094745">"3 ώρες"</item>
-    <item msgid="5263928189935026559">"12 ώρες"</item>
-    <item msgid="6451763179435252852">"24 ώρες"</item>
-    <item msgid="966666856467721784">"2 ημέρες"</item>
-    <item msgid="5840973497558794742">"1 εβδομάδα"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Προειδοποίηση"</item>
-    <item msgid="8723674378160518790">"Ειδοποίηση γραμμής κατάστασης"</item>
-    <item msgid="8433736231022186661">"Κλειστό"</item>
+    <item msgid="1746966478800108111">"Βίντχουκ"</item>
+    <item msgid="7008598576103662879">"Ώρα Δ. Αφρικής"</item>
+    <item msgid="1358916275855835758">"Αμμάν, Ιορδανία"</item>
+    <item msgid="5900185968201011015">"Αθήνα, Κωνσταντ."</item>
+    <item msgid="4975304496845689895">"Βηρυτός, Λίβανος"</item>
+    <item msgid="4099790960681254059">"Κάιρο"</item>
+    <item msgid="4174072690298729061">"Ελσίνκι"</item>
+    <item msgid="5233437715028921599">"Ιερουσαλήμ"</item>
+    <item msgid="612137838544847325">"Μινσκ"</item>
+    <item msgid="385832033162858704">"Χαράρε"</item>
+    <item msgid="1439592855284837653">"Βαγδάτη"</item>
+    <item msgid="3703352987270492256">"Μόσχα"</item>
+    <item msgid="1657996636394546903">"Κουβέιτ"</item>
+    <item msgid="3719675800096223903">"Ναϊρόμπι"</item>
+    <item msgid="4216389209911081345">"Τεχεράνη"</item>
+    <item msgid="4949871918709805735">"Μπακού"</item>
+    <item msgid="2863183321532922311">"Τιφλίδα"</item>
+    <item msgid="1943529325618264999">"Γερεβάν"</item>
+    <item msgid="5094507376721687886">"Ντουμπάι"</item>
+    <item msgid="2849654571454095226">"Καμπούλ"</item>
+    <item msgid="3641915655045056824">"Ισλ., Καράτσι"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Εκατερίνμπουργκ"</item>
+    <item msgid="297412491658718331">"Καλκούτα"</item>
+    <item msgid="2290076787008042202">"Σρι Λάνκα"</item>
+    <item msgid="4119703758417825749">"Κατμαντού"</item>
+    <item msgid="9161760299912900568">"Αστάνα"</item>
+    <item msgid="5753596907402377327">"Γιανγκόν"</item>
+    <item msgid="6275146302340764856">"Κρασνογιάρσκ"</item>
+    <item msgid="2385588555356900033">"Μπανγκόκ"</item>
+    <item msgid="7651147941145227340">"Πεκίνο"</item>
+    <item msgid="7831696303531874014">"Χονγκ Κονγκ"</item>
+    <item msgid="7943498021949728203">"Ιρκούτσκ"</item>
+    <item msgid="707009198021550118">"Κουάλα Λουμπούρ"</item>
+    <item msgid="4268864549735629220">"Περθ"</item>
+    <item msgid="4584239166170514795">"Ταϊπέι"</item>
+    <item msgid="4972387553080120086">"Σεούλ"</item>
+    <item msgid="5749349714875345358">"Τόκιο, Οσάκα"</item>
+    <item msgid="4435870687083770252">"Γιακούτσκ"</item>
+    <item msgid="4857952326324510625">"Αδελαίδα"</item>
+    <item msgid="7802545626741434274">"Ντάργουιν"</item>
+    <item msgid="7184691104734480971">"Μπρισμπέιν"</item>
+    <item msgid="7795651165061255022">"Χόμπαρτ"</item>
+    <item msgid="1858052331367859514">"Σύδνεϊ, Καμπέρα"</item>
+    <item msgid="4396794682654884440">"Βλαδιβοστόκ"</item>
+    <item msgid="4152655496917506653">"Γκουάμ"</item>
+    <item msgid="3608508135351982746">"Μάγκανταν"</item>
+    <item msgid="722180563708274099">"Όκλαντ"</item>
+    <item msgid="1736037231626139044">"Φίτζι"</item>
+    <item msgid="8187746821081424875">"Τόνγκα"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Απασχολημένος/η"</item>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d4aef98..3a7c988 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Ημερολόγιο"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Ημερολόγιο AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Τι"</string>
     <string name="where_label" msgid="3472482206065595118">"Που"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Ζώνη ώρας"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Ζώνη ώρας"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Προσκεκλημένοι"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Επανάληψη"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 ημέρα"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> ημέρες"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Ανανέωση"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Εμφάνιση ατζέντας"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Εμφάνιση ημέρας"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Ατζέντα"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Σήμερα"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Ημερολόγια"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Ρυθμίσεις"</string>
+    <string name="search" msgid="550656004486017426">"Αναζήτηση"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(και <xliff:g id="MORE_COUNT">%d</xliff:g> ακόμα…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Ημερολόγια"</string>
     <string name="synced_visible" msgid="1406744353749335651">"συγχρονίστηκε, είναι ορατό"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"συγχρονίστηκε, δεν είναι ορατό"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"δεν συγχρονίστηκε, δεν είναι ορατό"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Λεπτομέρειες συμβάντος"</string>
     <string name="hint_what" msgid="709155115005044531">"Όνομα συμβάντος"</string>
     <string name="hint_where" msgid="3116239630502213241">"Τοποθεσία συμβάντος"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Ημερολόγιο"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Εμφάνιση επιπρόσθετων επιλογών"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Απόκρυψη επιπρόσθετων επιλογών"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Εμφάνιση όλων &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Εμφάνιση όλων"</string>
     <string name="description_label" msgid="7193475046717505855">"Περιγραφή"</string>
     <string name="presence_label" msgid="6169724148441175862">"Να εμφανίζομαι ως"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Απόρρητο"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Υπενθυμίσεις"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Δεν υπάρχουν ημερολόγια"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Δεν έχετε ημερολόγια."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Από ημερομηνία"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Από ώρα"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Έως ημερομηνία"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Έως ώρα"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Υπενθυμίσεις"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Κατάργηση υπενθύμισης"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Ημερολόγιο:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Διοργανωτής:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Τοπική ζώνη ώρας:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Θα παραβρεθείτε;"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Σήμερα, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Φόρτωση..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Προβολή συμβάντων από <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Πατήστε για να αναζητήσετε περισσότερα συμβάντα."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Προβολή συμβάντων έως <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Αγγίξτε για να αναζητήσετε περισσότερα."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Εμφάνιση εκδηλώσεων από τις<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Αγγίξτε για αναζήτηση για προηγούμενες εκδηλώσεις."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Εμφάνιση εκδηλώσεων έως τις<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Αγγίξτε για αναζήτηση για μεταγενέστερες εκδηλώσεις."</string>
+    <string name="search_title" msgid="8898015280250716254">"Αναζήτηση στα ημερολόγιά μου"</string>
     <string name="num_events" msgid="3351672964607162257">"Αριθμ. συμβάντων"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Επεξεργασία συμβάντος"</string>
     <string name="delete_label" msgid="874742811918030739">"Διαγραφή"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Διαγραφή συμβάντος"</string>
     <string name="save_label" msgid="2133599104834432589">"Τέλος"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Επαναφορά"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Άκυρο"</string>
     <string name="import_label" msgid="6993796574741477222">"Εισαγωγή"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Αναβολή όλων"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Παράβλεψη όλων"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Αυτό το συμβάν θα διαγραφεί."</string>
     <string name="delete_title" msgid="5143743507524995383">"Διαγραφή"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Αλλαγή απάντησης"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Ρυθμίσεις"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Ρυθμίσεις προβολής ημερολογίου"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Ρυθμίσεις υπενθύμισης"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Ρυθμίσεις υπενθύμισης"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Απόκρυψη συμβάντων που έχουν απορριφθεί"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Ορισμός προειδοποιήσεων &amp; ειδοποιήσεων"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Προειδοποιήσεις &amp; ειδοποιήσεις"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Έναρξη εβδομάδας:"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Έναρξη εβδομάδας:"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Εκκαθάριση ιστορικού αναζήτησης"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Κατάργηση όλων των αναζητήσεών σας"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Έγινε εκκαθάριση ιστορικού αναζήτησης"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Ειδοποιήσεις"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Δόνηση"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Δόνηση και στις υπενθυμίσεις"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Επιλογή ήχου κλήσης"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Αναδυόμενη ειδοποίηση"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Προεπιλεγμένος χρόνος υπενθύμισης"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Προεπιλεγμένος χρόνος υπενθύμισης"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,13 @@
     <item msgid="4552962311897985633">"Ποτέ"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Δόνηση"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Ημερολόγιο"</string>
+    <!-- no translation found for gadget_more_events:one (1813062802420615657) -->
+    <!-- no translation found for gadget_more_events:other (7993512762778431065) -->
+    <!-- no translation found for gadget_no_events (5209110911088302855) -->
+    <skip />
+    <string name="tomorrow" msgid="614434811813859237">"Αύριο"</string>
+    <string name="in_progress" msgid="3637799356103489011">"σε εξέλιξη"</string>
+    <string name="today" msgid="8577375020252453006">"Σήμερα"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Αναζήτηση <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-es-rUS/arrays.xml b/res/values-es-rUS/arrays.xml
index 7ce95bd..984d278 100644
--- a/res/values-es-rUS/arrays.xml
+++ b/res/values-es-rUS/arrays.xml
@@ -33,9 +33,33 @@
     <item msgid="6908283876728494719">"2 días"</item>
     <item msgid="3390146235648970198">"1 semana"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Ninguno"</item>
+    <item msgid="3377199512833209480">"1 minuto"</item>
+    <item msgid="5023205715881387516">"5 minutos"</item>
+    <item msgid="7720870540593319107">"10 minutos"</item>
+    <item msgid="1653841700859180907">"15 minutos"</item>
+    <item msgid="3741629746313107482">"20 minutos"</item>
+    <item msgid="5357458446224446398">"25 minutos"</item>
+    <item msgid="403143749684280703">"30 minutos"</item>
+    <item msgid="5611574008997362948">"45 minutos"</item>
+    <item msgid="30462793717552">"1 hora"</item>
+    <item msgid="119808229352802480">"2 horas"</item>
+    <item msgid="7456742278252094745">"3 horas"</item>
+    <item msgid="5263928189935026559">"12 horas"</item>
+    <item msgid="6451763179435252852">"24 horas"</item>
+    <item msgid="966666856467721784">"2 días"</item>
+    <item msgid="5840973497558794742">"1 semana"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Configuración regional predeterminada"</item>
+    <item msgid="134027225275475280">"Sábado"</item>
+    <item msgid="95029346069903091">"Domingo"</item>
+    <item msgid="5840983116375063739">"Lunes"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Islas Marshall"</item>
-    <item msgid="7176569939440505575">"Islas Midway"</item>
+    <item msgid="7176569939440505575">"Islas Midway "</item>
     <item msgid="5000628410717488070">"Hawái"</item>
     <item msgid="3809977082338992040">"Alaska"</item>
     <item msgid="1206600898319778734">"Hora del Pacífico"</item>
@@ -60,87 +84,63 @@
     <item msgid="2287491943472924232">"Montevideo"</item>
     <item msgid="6337125611768317903">"Medio Atlántico"</item>
     <item msgid="1083966762112959704">"Azores"</item>
-    <item msgid="3208435599095023369">"Islas de Cabo Verde"</item>
+    <item msgid="3208435599095023369">"Islas de Cabo Verde\n "</item>
     <item msgid="8380333624463354109">"Casablanca"</item>
     <item msgid="8737442113188999866">"Londres, Dublín"</item>
     <item msgid="72424340270348400">"Amsterdam, Berlín"</item>
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Bruselas"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"París"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Tiempo de África Occidental"</item>
-    <item msgid="7527935722014187591">"Amman, Jordania"</item>
-    <item msgid="672509109295227133">"Atenas, Estambul"</item>
-    <item msgid="1835470340349221646">"Beirut, Líbano"</item>
-    <item msgid="9006223109403693650">"Cairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerusalén"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moscú"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teherán"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tiflis"</item>
-    <item msgid="3344801680659888795">"Yereván"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Oral"</item>
-    <item msgid="4757644551253886090">"Ekaterimburgo"</item>
-    <item msgid="3036512479614493957">"Calcuta"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandú"</item>
-    <item msgid="1195461607876034232">"Astaná"</item>
-    <item msgid="8777509327730950228">"Rangún"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Pekín"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seúl"</item>
-    <item msgid="6369124969606372122">"Tokio, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaida"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadán"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiyi"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Ninguno"</item>
-    <item msgid="3377199512833209480">"1 minuto"</item>
-    <item msgid="5023205715881387516">"5 minutos"</item>
-    <item msgid="7720870540593319107">"10 minutos"</item>
-    <item msgid="1653841700859180907">"15 minutos"</item>
-    <item msgid="3741629746313107482">"20 minutos"</item>
-    <item msgid="5357458446224446398">"25 minutos"</item>
-    <item msgid="403143749684280703">"30 minutos"</item>
-    <item msgid="5611574008997362948">"45 minutos"</item>
-    <item msgid="30462793717552">"1 hora"</item>
-    <item msgid="119808229352802480">"2 horas"</item>
-    <item msgid="7456742278252094745">"3 horas"</item>
-    <item msgid="5263928189935026559">"12 horas"</item>
-    <item msgid="6451763179435252852">"24 horas"</item>
-    <item msgid="966666856467721784">"2 días"</item>
-    <item msgid="5840973497558794742">"1 semana"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alerta"</item>
-    <item msgid="8723674378160518790">"Notificación de barra de estado"</item>
-    <item msgid="8433736231022186661">"Apagado"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Tiempo de África Occidental"</item>
+    <item msgid="1358916275855835758">"Amman, Jordania"</item>
+    <item msgid="5900185968201011015">"Atenas, Estambul"</item>
+    <item msgid="4975304496845689895">"Beirut, Líbano"</item>
+    <item msgid="4099790960681254059">"Cairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerusalén"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moscú"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teherán"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tiflis"</item>
+    <item msgid="1943529325618264999">"Yereván"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Oral"</item>
+    <item msgid="8779467771115356898">"Ekaterimburgo"</item>
+    <item msgid="297412491658718331">"Calcuta"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandú"</item>
+    <item msgid="9161760299912900568">"Astaná"</item>
+    <item msgid="5753596907402377327">"Rangún"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Pekín"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seúl"</item>
+    <item msgid="5749349714875345358">"Tokio, Osaka "</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaida"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadán"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiyi"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Ocupado"</item>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 818c2b9..a579a63 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Calendario"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendario todo en uno "</string>
     <string name="what_label" msgid="1933578391985810049">"Qué"</string>
     <string name="where_label" msgid="3472482206065595118">"Dónde"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Zona horaria"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Zona horaria"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Invitados"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Repetición"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"un día"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> días"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Actualizar"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Mostrar agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Mostrar día"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Hoy"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Calendarios"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Configuración"</string>
+    <string name="search" msgid="550656004486017426">"Buscar"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(más <xliff:g id="MORE_COUNT">%d</xliff:g> más …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Calendarios"</string>
     <string name="synced_visible" msgid="1406744353749335651">"sincronizada, visible"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"sincronizada, no visible"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"no sincronizada, no visible"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Detalles del evento"</string>
     <string name="hint_what" msgid="709155115005044531">"Nombre del evento"</string>
     <string name="hint_where" msgid="3116239630502213241">"Ubicación del evento"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Calendario"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Mostrar opciones adicionales"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ocultar opciones adicionales"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Mostrar todos &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Mostrar todos"</string>
     <string name="description_label" msgid="7193475046717505855">"Descripción"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mostrarme como"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacidad"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Recordatorios"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"No hay calendarios"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"No tienes calendarios."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"A partir del"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Desde las"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"A la fecha"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Medir el tiempo"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Recordatorios"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Eliminar recordatorio"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Calendario:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizador:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Zona horaria local:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"¿Asistirás?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Hoy, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Cargando…"</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Mostrando eventos desde <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Presionar para buscar más."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Mostrando eventos hasta <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Presionar para buscar más."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Mostrando eventos desde el <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tocar para buscar los eventos anteriores."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Mostrando eventos hasta el <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toca para buscar los últimos eventos."</string>
+    <string name="search_title" msgid="8898015280250716254">"Buscar en mis calendarios"</string>
     <string name="num_events" msgid="3351672964607162257">"Cant. de eventos"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Editar evento"</string>
     <string name="delete_label" msgid="874742811918030739">"Suprimir"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Suprimir evento"</string>
     <string name="save_label" msgid="2133599104834432589">"Finalizado"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Revertir"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Cancelar"</string>
     <string name="import_label" msgid="6993796574741477222">"Importar"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Posponer todos"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Rechazar todos"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Este evento se suprimirá."</string>
     <string name="delete_title" msgid="5143743507524995383">"Suprimir"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Cambiar respuesta"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Configuración"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Configuración de vista de calendario"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Configuración de recordatorio"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Configuración de recordatorio"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Ocultar eventos rechaz."</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Config. alertas y notific."</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alertas y notificaciones"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"La semana comienza el"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"La semana comienza el"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Borrar historial de búsqueda"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Eliminar todas las búsquedas que has realizado"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Se borró el historial de búsqueda"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notificaciones"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrar"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"También vibrar para recordatorios"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Selec. tono de llam."</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notificación emergente"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Hora predet. de record."</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Hora predet. de record."</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,13 @@
     <item msgid="4552962311897985633">"Nunca"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrar"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Calendario"</string>
+    <!-- no translation found for gadget_more_events:one (1813062802420615657) -->
+    <!-- no translation found for gadget_more_events:other (7993512762778431065) -->
+    <!-- no translation found for gadget_no_events (5209110911088302855) -->
+    <skip />
+    <string name="tomorrow" msgid="614434811813859237">"Mañana"</string>
+    <string name="in_progress" msgid="3637799356103489011">"en progreso"</string>
+    <string name="today" msgid="8577375020252453006">"Hoy"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Buscando <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml
index c10bd94..9638bd9 100644
--- a/res/values-es/arrays.xml
+++ b/res/values-es/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 días"</item>
     <item msgid="3390146235648970198">"1 semana"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Ninguno"</item>
+    <item msgid="3377199512833209480">"1 minuto"</item>
+    <item msgid="5023205715881387516">"5 minutos"</item>
+    <item msgid="7720870540593319107">"10 minutos"</item>
+    <item msgid="1653841700859180907">"15 minutos"</item>
+    <item msgid="3741629746313107482">"20 minutos"</item>
+    <item msgid="5357458446224446398">"25 minutos"</item>
+    <item msgid="403143749684280703">"30 minutos"</item>
+    <item msgid="5611574008997362948">"45 minutos"</item>
+    <item msgid="30462793717552">"1 hora"</item>
+    <item msgid="119808229352802480">"2 horas"</item>
+    <item msgid="7456742278252094745">"3 horas"</item>
+    <item msgid="5263928189935026559">"12 horas"</item>
+    <item msgid="6451763179435252852">"24 horas"</item>
+    <item msgid="966666856467721784">"2 días"</item>
+    <item msgid="5840973497558794742">"1 semana"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Región predeterminada"</item>
+    <item msgid="134027225275475280">"Sábado"</item>
+    <item msgid="95029346069903091">"Domingo"</item>
+    <item msgid="5840983116375063739">"Lunes"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Islas Marshall"</item>
     <item msgid="7176569939440505575">"Islas Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Bruselas"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"París"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Hora África Occidental"</item>
-    <item msgid="7527935722014187591">"Ammán, Jordania"</item>
-    <item msgid="672509109295227133">"Atenas, Estambul"</item>
-    <item msgid="1835470340349221646">"Beirut, Líbano"</item>
-    <item msgid="9006223109403693650">"El Cairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerusalén"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moscú"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teherán"</item>
-    <item msgid="141548505188106312">"Bakú"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Yerevan"</item>
-    <item msgid="2537944335416132075">"Dubái"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Oral"</item>
-    <item msgid="4757644551253886090">"Ekaterimburgo"</item>
-    <item msgid="3036512479614493957">"Calcuta"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandú"</item>
-    <item msgid="1195461607876034232">"Astaná"</item>
-    <item msgid="8777509327730950228">"Rangún"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Pekín"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipéi"</item>
-    <item msgid="3043691515163887690">"Seúl"</item>
-    <item msgid="6369124969606372122">"Tokio, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaida"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadán"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiyi"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Ninguno"</item>
-    <item msgid="3377199512833209480">"1 minuto"</item>
-    <item msgid="5023205715881387516">"5 minutos"</item>
-    <item msgid="7720870540593319107">"10 minutos"</item>
-    <item msgid="1653841700859180907">"15 minutos"</item>
-    <item msgid="3741629746313107482">"20 minutos"</item>
-    <item msgid="5357458446224446398">"25 minutos"</item>
-    <item msgid="403143749684280703">"30 minutos"</item>
-    <item msgid="5611574008997362948">"45 minutos"</item>
-    <item msgid="30462793717552">"1 hora"</item>
-    <item msgid="119808229352802480">"2 horas"</item>
-    <item msgid="7456742278252094745">"3 horas"</item>
-    <item msgid="5263928189935026559">"12 horas"</item>
-    <item msgid="6451763179435252852">"24 horas"</item>
-    <item msgid="966666856467721784">"2 días"</item>
-    <item msgid="5840973497558794742">"1 semana"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alerta"</item>
-    <item msgid="8723674378160518790">"Notificación en la barra de estado"</item>
-    <item msgid="8433736231022186661">"Desconectado"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Hora África Occ"</item>
+    <item msgid="1358916275855835758">"Ammán, Jordania"</item>
+    <item msgid="5900185968201011015">"Atenas, Estambul"</item>
+    <item msgid="4975304496845689895">"Beirut, Líbano"</item>
+    <item msgid="4099790960681254059">"El Cairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerusalén"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moscú"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teherán"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Yerevan"</item>
+    <item msgid="5094507376721687886">"Dubái"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Oral"</item>
+    <item msgid="8779467771115356898">"Ekaterimburgo"</item>
+    <item msgid="297412491658718331">"Calcuta"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandú"</item>
+    <item msgid="9161760299912900568">"Astaná"</item>
+    <item msgid="5753596907402377327">"Rangún"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Pekín"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipéi"</item>
+    <item msgid="4972387553080120086">"Seúl"</item>
+    <item msgid="5749349714875345358">"Tokio, Osaka"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaida"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadán"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiyi"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Ocupado"</item>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index e2d930f..5bb4383 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Calendario"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendario AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Asunto"</string>
     <string name="where_label" msgid="3472482206065595118">"Dónde"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Zona horaria"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Zona horaria"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Invitados"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Repetición"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 día"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> días"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Actualizar"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Mostrar agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Mostrar día"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Hoy"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Calendarios"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Ajustes"</string>
+    <string name="search" msgid="550656004486017426">"Buscar"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(además de <xliff:g id="MORE_COUNT">%d</xliff:g> más...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Calendarios"</string>
     <string name="synced_visible" msgid="1406744353749335651">"sincronizado y visible"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"sincronizado, pero no visible"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"no sincronizado o no visible"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Información del evento"</string>
     <string name="hint_what" msgid="709155115005044531">"Nombre del evento"</string>
     <string name="hint_where" msgid="3116239630502213241">"Ubicación del evento"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Calendario"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Mostrar opciones extra"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ocultar opciones extra"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Mostrar todo &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Mostrar todo"</string>
     <string name="description_label" msgid="7193475046717505855">"Descripción"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mostrarme como"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacidad"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Recordatorios"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"No hay calendarios."</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"No tienes ningún calendario."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Desde la fecha"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Desde la hora"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Hasta la fecha"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Hasta la hora"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Recordatorios"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Eliminar recordatorio"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Calendario:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizador:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Zona horaria local:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"¿Asistirás?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Hoy, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Cargando..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Mostrando eventos a partir del <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toca aquí para buscar más."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Mostrando eventos hasta el <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toca aquí para buscar más."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Mostrando eventos desde el <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toca para buscar eventos anteriores."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Mostrando eventos hasta el <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toca para buscar eventos posteriores."</string>
+    <string name="search_title" msgid="8898015280250716254">"Buscar en mis calendarios"</string>
     <string name="num_events" msgid="3351672964607162257">"Número de eventos"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Editar evento"</string>
     <string name="delete_label" msgid="874742811918030739">"Eliminar"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Suprimir evento"</string>
     <string name="save_label" msgid="2133599104834432589">"OK"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Revertir"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Cancelar"</string>
     <string name="import_label" msgid="6993796574741477222">"Importar"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Posponer recordatorios"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Descartar todo"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Este evento se suprimirá."</string>
     <string name="delete_title" msgid="5143743507524995383">"Eliminar"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Cambiar respuesta"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Ajustes"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Configuración de la vista de calendario"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Configuración de los recordatorios"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Configuración de los recordatorios"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Ocultar eventos rechaz."</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Configurar alertas y notif."</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alertas y notificaciones"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"La semana empieza el"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"La semana empieza el"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Borrar historial de búsqueda"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Eliminar todas las búsquedas realizadas"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Se ha borrado el historial de búsqueda."</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notificaciones"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrar"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrar también para recordatorios"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Seleccionar tono"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notificación emergente"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Duración predeterminada del recordatorio"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Duración predeterminada del recordatorio"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nunca"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibración"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Calendario"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"Un evento más"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> eventos más"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"No hay próximos eventos en el calendario"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Mañana"</string>
+    <string name="in_progress" msgid="3637799356103489011">"en curso"</string>
+    <string name="today" msgid="8577375020252453006">"Hoy"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Buscando <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
index 3dfcf17..c5bc2b0 100644
--- a/res/values-fr/arrays.xml
+++ b/res/values-fr/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 jours"</item>
     <item msgid="3390146235648970198">"1 semaine"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Aucun"</item>
+    <item msgid="3377199512833209480">"1 minute"</item>
+    <item msgid="5023205715881387516">"5 minutes"</item>
+    <item msgid="7720870540593319107">"10 minutes"</item>
+    <item msgid="1653841700859180907">"15 minutes"</item>
+    <item msgid="3741629746313107482">"20 minutes"</item>
+    <item msgid="5357458446224446398">"25 minutes"</item>
+    <item msgid="403143749684280703">"30 minutes"</item>
+    <item msgid="5611574008997362948">"45 minutes"</item>
+    <item msgid="30462793717552">"1 heure"</item>
+    <item msgid="119808229352802480">"2 heures"</item>
+    <item msgid="7456742278252094745">"3 heures"</item>
+    <item msgid="5263928189935026559">"12 heures"</item>
+    <item msgid="6451763179435252852">"24 heures"</item>
+    <item msgid="966666856467721784">"2 jours"</item>
+    <item msgid="5840973497558794742">"1 semaine"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Paramètres régionaux par défaut"</item>
+    <item msgid="134027225275475280">"Samedi"</item>
+    <item msgid="95029346069903091">"Dimanche"</item>
+    <item msgid="5840983116375063739">"Lundi"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshall (Îles)"</item>
     <item msgid="7176569939440505575">"Atoll de Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrade"</item>
     <item msgid="3284290759549286849">"Bruxelles"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Afrique occid."</item>
-    <item msgid="7527935722014187591">"Amman, Jordanie"</item>
-    <item msgid="672509109295227133">"Athènes/Istanbul"</item>
-    <item msgid="1835470340349221646">"Beyrouth, Liban"</item>
-    <item msgid="9006223109403693650">"Le Caire"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jérusalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moscou"</item>
-    <item msgid="6103383184232804522">"Koweït"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Téhéran"</item>
-    <item msgid="141548505188106312">"Bakou"</item>
-    <item msgid="5974315340250149692">"Tbilissi"</item>
-    <item msgid="3344801680659888795">"Erevan"</item>
-    <item msgid="2537944335416132075">"Dubaï"</item>
-    <item msgid="5630432498109110949">"Kaboul"</item>
-    <item msgid="2274762525650993846">"Islamabad/Karachi"</item>
-    <item msgid="272153171103339242">"Uralsk"</item>
-    <item msgid="4757644551253886090">"Ekaterinbourg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandou"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoïarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Beijing"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkoutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Séoul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Iakoutsk"</item>
-    <item msgid="6993206457323990990">"Adélaïde"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fidji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Aucun"</item>
-    <item msgid="3377199512833209480">"1 minute"</item>
-    <item msgid="5023205715881387516">"5 minutes"</item>
-    <item msgid="7720870540593319107">"10 minutes"</item>
-    <item msgid="1653841700859180907">"15 minutes"</item>
-    <item msgid="3741629746313107482">"20 minutes"</item>
-    <item msgid="5357458446224446398">"25 minutes"</item>
-    <item msgid="403143749684280703">"30 minutes"</item>
-    <item msgid="5611574008997362948">"45 minutes"</item>
-    <item msgid="30462793717552">"1 heure"</item>
-    <item msgid="119808229352802480">"2 heures"</item>
-    <item msgid="7456742278252094745">"3 heures"</item>
-    <item msgid="5263928189935026559">"12 heures"</item>
-    <item msgid="6451763179435252852">"24 heures"</item>
-    <item msgid="966666856467721784">"2 jours"</item>
-    <item msgid="5840973497558794742">"1 semaine"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alerte"</item>
-    <item msgid="8723674378160518790">"Barre d\'état"</item>
-    <item msgid="8433736231022186661">"Désactiver"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Afrique occid."</item>
+    <item msgid="1358916275855835758">"Amman, Jordanie"</item>
+    <item msgid="5900185968201011015">"Athènes/Istanbul"</item>
+    <item msgid="4975304496845689895">"Beyrouth, Liban"</item>
+    <item msgid="4099790960681254059">"Le Caire"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jérusalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moscou"</item>
+    <item msgid="1657996636394546903">"Koweït"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Téhéran"</item>
+    <item msgid="4949871918709805735">"Bakou"</item>
+    <item msgid="2863183321532922311">"Tbilissi"</item>
+    <item msgid="1943529325618264999">"Erevan"</item>
+    <item msgid="5094507376721687886">"Dubaï"</item>
+    <item msgid="2849654571454095226">"Kaboul"</item>
+    <item msgid="3641915655045056824">"Islamabad/Karachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Ekaterinbourg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandou"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoïarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Beijing"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkoutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Séoul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Iakoutsk"</item>
+    <item msgid="4857952326324510625">"Adélaïde"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji (Îles)"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Occupé"</item>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index aa980b9..cd39abb 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Agenda"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendar AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Objet"</string>
     <string name="where_label" msgid="3472482206065595118">"Lieu"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Fuseau horaire"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Fuseau horaire"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Invités"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Fréquence"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 jour"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> jours"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Actualiser"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Afficher l\'agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Afficher le jour"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Mon planning"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Aujourd\'hui"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Agendas"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Paramètres"</string>
+    <string name="search" msgid="550656004486017426">"Rechercher"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(et <xliff:g id="MORE_COUNT">%d</xliff:g> autres…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Agendas"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synchronisé, visible"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synchronisé, non visible"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"non synchronisé, non visible"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Détails sur l\'événement"</string>
     <string name="hint_what" msgid="709155115005044531">"Nom de l\'événement"</string>
     <string name="hint_where" msgid="3116239630502213241">"Lieu de l\'événement"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Agenda"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Afficher les options supplémentaires"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Masquer options supplémentaires"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Tout afficher &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Tout afficher"</string>
     <string name="description_label" msgid="7193475046717505855">"Description"</string>
     <string name="presence_label" msgid="6169724148441175862">"Ma disponibilité"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Confidentialité"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Rappels"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Pas d\'agenda"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Vous n\'avez pas d\'agenda."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Du"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"De"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Au"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"À"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Rappels"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Supprimer le rappel"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Agenda :"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organisateur :"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Fuseau horaire local :"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Participation ?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Aujourd\'hui, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Chargement…"</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Affichage des événements depuis le <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Appuyez pour en consulter d\'autres."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Affichage des événements jusqu\'au <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Appuyez pour en consulter d\'autres."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Événements de <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Appuyez sur l\'écran pour rechercher des événements antérieurs."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Événements de <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Appuyez sur l\'écran pour rechercher des événements ultérieurs."</string>
+    <string name="search_title" msgid="8898015280250716254">"Rechercher dans mes agendas"</string>
     <string name="num_events" msgid="3351672964607162257">"Nbre d\'événements"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Modifier l\'événement"</string>
     <string name="delete_label" msgid="874742811918030739">"Supprimer"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Supprimer l\'événement"</string>
     <string name="save_label" msgid="2133599104834432589">"OK"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Annuler"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Annuler"</string>
     <string name="import_label" msgid="6993796574741477222">"Importer"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Tout répéter"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Tout supprimer"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Cet événement va être supprimé."</string>
     <string name="delete_title" msgid="5143743507524995383">"Supprimer"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Modifier la réponse"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Paramètres"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Paramètres d\'affichage de l\'agenda"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Paramètres de rappel"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Paramètres de rappel"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Masquer évén. refusés"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Alertes et notifications"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alertes et notifications"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"1er jour de la semaine :"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"1er jour de la semaine :"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Effacer l\'historique des recherches"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Supprimer toutes les recherches que vous avez effectuées"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Historique des recherches effacé"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notifications"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibreur"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrer également pour les rappels"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Sonnerie"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notification dans une fenêtre pop-up"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Rappel par défaut"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Rappel par défaut"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Jamais"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibreur"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Agenda"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"1 événement supplémentaire"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> autres événements"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Aucun événement d\'agenda à venir"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Demain"</string>
+    <string name="in_progress" msgid="3637799356103489011">"en cours"</string>
+    <string name="today" msgid="8577375020252453006">"Aujourd\'hui"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Recherche en cours dans <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml
index 8824422..c4b008d 100644
--- a/res/values-it/arrays.xml
+++ b/res/values-it/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 giorni"</item>
     <item msgid="3390146235648970198">"1 settimana"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Nessuno"</item>
+    <item msgid="3377199512833209480">"1 minuto"</item>
+    <item msgid="5023205715881387516">"5 minuti"</item>
+    <item msgid="7720870540593319107">"10 minuti"</item>
+    <item msgid="1653841700859180907">"15 minuti"</item>
+    <item msgid="3741629746313107482">"20 minuti"</item>
+    <item msgid="5357458446224446398">"25 minuti"</item>
+    <item msgid="403143749684280703">"30 minuti"</item>
+    <item msgid="5611574008997362948">"45 minuti"</item>
+    <item msgid="30462793717552">"1 ora"</item>
+    <item msgid="119808229352802480">"2 ore"</item>
+    <item msgid="7456742278252094745">"3 ore"</item>
+    <item msgid="5263928189935026559">"12 ore"</item>
+    <item msgid="6451763179435252852">"24 ore"</item>
+    <item msgid="966666856467721784">"2 giorni"</item>
+    <item msgid="5840973497558794742">"1 settimana"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Impostazioni internazionali predefinite"</item>
+    <item msgid="134027225275475280">"Sabato"</item>
+    <item msgid="95029346069903091">"Domenica"</item>
+    <item msgid="5840983116375063739">"Lunedì"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Isole Marshall"</item>
     <item msgid="7176569939440505575">"Isola di Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Bruxelles"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Parigi"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"W. Africa Time"</item>
-    <item msgid="7527935722014187591">"Amman, Giordania"</item>
-    <item msgid="672509109295227133">"Atene, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beirut, Libano"</item>
-    <item msgid="9006223109403693650">"Cairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Gerusalemme"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Baghdad"</item>
-    <item msgid="8077211460729251609">"Mosca"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Yerevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Ekaterinburg"</item>
-    <item msgid="3036512479614493957">"Calcutta"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Kathmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Pechino"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Jakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Figi"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Nessuno"</item>
-    <item msgid="3377199512833209480">"1 minuto"</item>
-    <item msgid="5023205715881387516">"5 minuti"</item>
-    <item msgid="7720870540593319107">"10 minuti"</item>
-    <item msgid="1653841700859180907">"15 minuti"</item>
-    <item msgid="3741629746313107482">"20 minuti"</item>
-    <item msgid="5357458446224446398">"25 minuti"</item>
-    <item msgid="403143749684280703">"30 minuti"</item>
-    <item msgid="5611574008997362948">"45 minuti"</item>
-    <item msgid="30462793717552">"1 ora"</item>
-    <item msgid="119808229352802480">"2 ore"</item>
-    <item msgid="7456742278252094745">"3 ore"</item>
-    <item msgid="5263928189935026559">"12 ore"</item>
-    <item msgid="6451763179435252852">"24 ore"</item>
-    <item msgid="966666856467721784">"2 giorni"</item>
-    <item msgid="5840973497558794742">"1 settimana"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Avviso"</item>
-    <item msgid="8723674378160518790">"Notifica barra di stato"</item>
-    <item msgid="8433736231022186661">"Disattiva"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"W. Africa Time"</item>
+    <item msgid="1358916275855835758">"Amman, Giordania"</item>
+    <item msgid="5900185968201011015">"Atene, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beirut, Libano"</item>
+    <item msgid="4099790960681254059">"Cairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Gerusalemme"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Baghdad"</item>
+    <item msgid="3703352987270492256">"Mosca"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Yerevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Ekaterinburg"</item>
+    <item msgid="297412491658718331">"Calcutta"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Pechino"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Jakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Figi"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Occupato"</item>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 3f39945..f30129a 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Calendario"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendar AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Che cosa"</string>
     <string name="where_label" msgid="3472482206065595118">"Dove"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Fuso orario"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Fuso orario"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Invitati"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Ripetizione"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 giorno"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> giorni"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Aggiorna"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Mostra agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Mostra giorno"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Oggi"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Calendari"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Impostazioni"</string>
+    <string name="search" msgid="550656004486017426">"Cerca"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(più altri <xliff:g id="MORE_COUNT">%d</xliff:g>…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Calendari"</string>
     <string name="synced_visible" msgid="1406744353749335651">"sincronizzato, visibile"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"sincronizzato, non visibile"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"non sincronizzato, non visibile"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Dettagli evento"</string>
     <string name="hint_what" msgid="709155115005044531">"Nome evento"</string>
     <string name="hint_where" msgid="3116239630502213241">"Luogo evento"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Calendario"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Mostra opzioni aggiuntive"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Nascondi opzioni aggiuntive"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Mostra tutto &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Mostra tutto"</string>
     <string name="description_label" msgid="7193475046717505855">"Descrizione"</string>
     <string name="presence_label" msgid="6169724148441175862">"Imposta il mio stato su"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacy"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Promemoria"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Nessun calendario"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Non sono presenti calendari."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Data di inizio"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Ora di inizio"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Data di fine"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Ora di fine"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Promemoria"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Rimuovi il promemoria"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Calendario:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Agenda:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Fuso orario locale:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Partecipi?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Oggi, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Caricamento..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Eventi visualizzati a partire da <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tocca per cercarne altri."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Eventi visualizzati fino a <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Tocca per cercarne altri."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Eventi mostrati da <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tocca per cercare eventi precedenti."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Eventi mostrati fino a <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Tocca per cercare eventi successivi."</string>
+    <string name="search_title" msgid="8898015280250716254">"Cerca nei miei calendari"</string>
     <string name="num_events" msgid="3351672964607162257">"N. eventi"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Modifica  evento"</string>
     <string name="delete_label" msgid="874742811918030739">"Elimina"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Elimina evento"</string>
     <string name="save_label" msgid="2133599104834432589">"Salva"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Annulla modifiche"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Annulla"</string>
     <string name="import_label" msgid="6993796574741477222">"Importa"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Posponi tutto"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Ignora tutto"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"L\'evento verrà eliminato."</string>
     <string name="delete_title" msgid="5143743507524995383">"Elimina"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Cambia risposta"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Impostazioni"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Impostazione visualizzazione calendario"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Impostazioni promemoria"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Impostazioni promemoria"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Nascondi eventi rifiutati"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Imposta avvisi e notif."</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Avvisi e notifiche"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"La settimana inizia da"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"La settimana inizia da"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Cancella cronologia ricerche"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Rimuovi tutte le ricerche che hai eseguito"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Cronologia ricerche cancellata"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notifiche"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrazione"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Attiva anche vibrazione per promemoria"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Seleziona suoneria"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notifica popup"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Imposta promem. pred."</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Imposta promem. pred."</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Mai"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrazione"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Calendario"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"1 altro evento"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> altri eventi"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Nessun evento di calendario imminente"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Domani"</string>
+    <string name="in_progress" msgid="3637799356103489011">"in corso"</string>
+    <string name="today" msgid="8577375020252453006">"Oggi"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Ricerca in <xliff:g id="DOMAIN">%s</xliff:g> in corso..."</string>
 </resources>
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
index 721183a..56863b4 100644
--- a/res/values-ja/arrays.xml
+++ b/res/values-ja/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2日間"</item>
     <item msgid="3390146235648970198">"1週間"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"なし"</item>
+    <item msgid="3377199512833209480">"1分"</item>
+    <item msgid="5023205715881387516">"5分"</item>
+    <item msgid="7720870540593319107">"10分"</item>
+    <item msgid="1653841700859180907">"15分"</item>
+    <item msgid="3741629746313107482">"20分"</item>
+    <item msgid="5357458446224446398">"25分"</item>
+    <item msgid="403143749684280703">"30分"</item>
+    <item msgid="5611574008997362948">"45分"</item>
+    <item msgid="30462793717552">"1時間"</item>
+    <item msgid="119808229352802480">"2時間"</item>
+    <item msgid="7456742278252094745">"3時間"</item>
+    <item msgid="5263928189935026559">"12時間"</item>
+    <item msgid="6451763179435252852">"24時間"</item>
+    <item msgid="966666856467721784">"2日間"</item>
+    <item msgid="5840973497558794742">"1週間"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"ユーザーの言語/地域でのデフォルト設定"</item>
+    <item msgid="134027225275475280">"土曜日"</item>
+    <item msgid="95029346069903091">"日曜日"</item>
+    <item msgid="5840983116375063739">"月曜日"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"マーシャル諸島"</item>
     <item msgid="7176569939440505575">"ミッドウェー諸島"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"ベオグラード"</item>
     <item msgid="3284290759549286849">"ブリュッセル"</item>
     <item msgid="3112943938015033049">"サラエボ"</item>
-    <item msgid="2184425710639965762">"パリ"</item>
-    <item msgid="5997630882187580225">"ウィントフック"</item>
-    <item msgid="3828800493609994208">"西アフリカ標準時"</item>
-    <item msgid="7527935722014187591">"アンマン、ヨルダン"</item>
-    <item msgid="672509109295227133">"アテネ、イスタンブール"</item>
-    <item msgid="1835470340349221646">"ベイルート、レバノン"</item>
-    <item msgid="9006223109403693650">"カイロ"</item>
-    <item msgid="2877875673636361825">"ヘルシンキ"</item>
-    <item msgid="4528124308590807049">"エルサレム"</item>
-    <item msgid="7823856457885290195">"ミンスク"</item>
-    <item msgid="8650995074282357197">"ハラーレ"</item>
-    <item msgid="209523815326871285">"バグダッド"</item>
-    <item msgid="8077211460729251609">"モスクワ"</item>
-    <item msgid="6103383184232804522">"クウェート"</item>
-    <item msgid="8581935265080466099">"ナイロビ"</item>
-    <item msgid="1531618843539764728">"テヘラン"</item>
-    <item msgid="141548505188106312">"バクー"</item>
-    <item msgid="5974315340250149692">"トビリシ"</item>
-    <item msgid="3344801680659888795">"エレバン"</item>
-    <item msgid="2537944335416132075">"ドバイ"</item>
-    <item msgid="5630432498109110949">"カブール"</item>
-    <item msgid="2274762525650993846">"イスラマーバード、カラチ"</item>
-    <item msgid="272153171103339242">"ウラリスク"</item>
-    <item msgid="4757644551253886090">"エカテリンブルグ"</item>
-    <item msgid="3036512479614493957">"コルカタ"</item>
-    <item msgid="4190271241277312277">"スリランカ"</item>
-    <item msgid="4489139826358657866">"カトマンズ"</item>
-    <item msgid="1195461607876034232">"アスタナ"</item>
-    <item msgid="8777509327730950228">"ヤンゴン"</item>
-    <item msgid="3838899720161825998">"クラスノヤルスク"</item>
-    <item msgid="6495532062164690437">"バンコク"</item>
-    <item msgid="4910549870048384487">"北京"</item>
-    <item msgid="7174954011332328153">"香港"</item>
-    <item msgid="415034182139280334">"イルクーツク"</item>
-    <item msgid="3407263784503834649">"クアラルンプール"</item>
-    <item msgid="6547945295249807755">"パース"</item>
-    <item msgid="4571776847411430193">"台北"</item>
-    <item msgid="3043691515163887690">"ソウル"</item>
-    <item msgid="6369124969606372122">"東京、大阪"</item>
-    <item msgid="8317842783685169193">"ヤクーツク"</item>
-    <item msgid="6993206457323990990">"アデレード"</item>
-    <item msgid="2431970257749452312">"ダーウィン"</item>
-    <item msgid="2709489188606506801">"ブリスベン"</item>
-    <item msgid="6832511138206913044">"ホバート"</item>
-    <item msgid="6933429790996727761">"シドニー、キャンベラ"</item>
-    <item msgid="4546625974877459074">"ウラジオストク"</item>
-    <item msgid="7421491250467489614">"グアム"</item>
-    <item msgid="3450367738093440984">"マガダン"</item>
-    <item msgid="5046540868552082971">"オークランド"</item>
-    <item msgid="481903191189494072">"フィジー"</item>
-    <item msgid="1926932394586102998">"トンガ"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"なし"</item>
-    <item msgid="3377199512833209480">"1分"</item>
-    <item msgid="5023205715881387516">"5分"</item>
-    <item msgid="7720870540593319107">"10分"</item>
-    <item msgid="1653841700859180907">"15分"</item>
-    <item msgid="3741629746313107482">"20分"</item>
-    <item msgid="5357458446224446398">"25分"</item>
-    <item msgid="403143749684280703">"30分"</item>
-    <item msgid="5611574008997362948">"45分"</item>
-    <item msgid="30462793717552">"1時間"</item>
-    <item msgid="119808229352802480">"2時間"</item>
-    <item msgid="7456742278252094745">"3時間"</item>
-    <item msgid="5263928189935026559">"12時間"</item>
-    <item msgid="6451763179435252852">"24時間"</item>
-    <item msgid="966666856467721784">"2日間"</item>
-    <item msgid="5840973497558794742">"1週間"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"アラート"</item>
-    <item msgid="8723674378160518790">"ステータスバー"</item>
-    <item msgid="8433736231022186661">"OFF"</item>
+    <item msgid="1746966478800108111">"ウィントフック"</item>
+    <item msgid="7008598576103662879">"西アフリカ標準時"</item>
+    <item msgid="1358916275855835758">"アンマン、ヨルダン"</item>
+    <item msgid="5900185968201011015">"アテネ、イスタンブール"</item>
+    <item msgid="4975304496845689895">"ベイルート、レバノン"</item>
+    <item msgid="4099790960681254059">"カイロ"</item>
+    <item msgid="4174072690298729061">"ヘルシンキ"</item>
+    <item msgid="5233437715028921599">"エルサレム"</item>
+    <item msgid="612137838544847325">"ミンスク"</item>
+    <item msgid="385832033162858704">"ハラーレ"</item>
+    <item msgid="1439592855284837653">"バグダッド"</item>
+    <item msgid="3703352987270492256">"モスクワ"</item>
+    <item msgid="1657996636394546903">"クウェート"</item>
+    <item msgid="3719675800096223903">"ナイロビ"</item>
+    <item msgid="4216389209911081345">"テヘラン"</item>
+    <item msgid="4949871918709805735">"バクー"</item>
+    <item msgid="2863183321532922311">"トビリシ"</item>
+    <item msgid="1943529325618264999">"エレバン"</item>
+    <item msgid="5094507376721687886">"ドバイ"</item>
+    <item msgid="2849654571454095226">"カブール"</item>
+    <item msgid="3641915655045056824">"イスラマーバード、カラチ"</item>
+    <item msgid="5795351343828254400">"ウラリスク"</item>
+    <item msgid="8779467771115356898">"エカテリンブルグ"</item>
+    <item msgid="297412491658718331">"コルカタ"</item>
+    <item msgid="2290076787008042202">"スリランカ"</item>
+    <item msgid="4119703758417825749">"カトマンズ"</item>
+    <item msgid="9161760299912900568">"アスタナ"</item>
+    <item msgid="5753596907402377327">"ヤンゴン"</item>
+    <item msgid="6275146302340764856">"クラスノヤルスク"</item>
+    <item msgid="2385588555356900033">"バンコク"</item>
+    <item msgid="7651147941145227340">"北京"</item>
+    <item msgid="7831696303531874014">"香港"</item>
+    <item msgid="7943498021949728203">"イルクーツク"</item>
+    <item msgid="707009198021550118">"クアラルンプール"</item>
+    <item msgid="4268864549735629220">"パース"</item>
+    <item msgid="4584239166170514795">"台北"</item>
+    <item msgid="4972387553080120086">"ソウル"</item>
+    <item msgid="5749349714875345358">"東京、大阪"</item>
+    <item msgid="4435870687083770252">"ヤクーツク"</item>
+    <item msgid="4857952326324510625">"アデレード"</item>
+    <item msgid="7802545626741434274">"ダーウィン"</item>
+    <item msgid="7184691104734480971">"ブリスベン"</item>
+    <item msgid="7795651165061255022">"ホバート"</item>
+    <item msgid="1858052331367859514">"シドニー、キャンベラ"</item>
+    <item msgid="4396794682654884440">"ウラジオストク"</item>
+    <item msgid="4152655496917506653">"グアム"</item>
+    <item msgid="3608508135351982746">"マガダン"</item>
+    <item msgid="722180563708274099">"オークランド"</item>
+    <item msgid="1736037231626139044">"フィジー"</item>
+    <item msgid="8187746821081424875">"トンガ"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"予定あり"</item>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 0cb34ff..7ca3435 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"カレンダー"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendar AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"タイトル"</string>
     <string name="where_label" msgid="3472482206065595118">"場所"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"タイムゾーン"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"タイムゾーン"</string>
     <string name="attendees_label" msgid="2971281641565729725">"ゲスト"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g>(<xliff:g id="GUEST_COUNT">%d</xliff:g>名)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"繰り返し"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1日"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g>日"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"更新"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"予定リストを表示"</string>
     <string name="show_day_view" msgid="5782512782605466016">"日付を表示"</string>
     <string name="agenda_view" msgid="5566084759423843245">"予定リスト"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"今日"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"カレンダー"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"設定"</string>
+    <string name="search" msgid="550656004486017426">"検索"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(あと <xliff:g id="MORE_COUNT">%d</xliff:g> 件...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"カレンダー"</string>
     <string name="synced_visible" msgid="1406744353749335651">"同期、表示"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"同期、非表示"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"未同期、非表示"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"予定の詳細"</string>
     <string name="hint_what" msgid="709155115005044531">"タイトル"</string>
     <string name="hint_where" msgid="3116239630502213241">"場所"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"カレンダー"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"詳細項目を表示"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"詳細項目を表示しない"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"すべて表示 &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"すべて表示"</string>
     <string name="description_label" msgid="7193475046717505855">"内容"</string>
     <string name="presence_label" msgid="6169724148441175862">"外部向け表示"</string>
     <string name="privacy_label" msgid="9215031965259597335">"公開設定"</string>
     <string name="reminders_label" msgid="8345054160145333166">"通知"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"カレンダーがありません"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"カレンダーがありません。"</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"開始日"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"開始時刻"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"終了日"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"終了時刻"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"通知"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"通知を削除"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"カレンダー:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"主催者:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"ローカルタイムゾーン:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"参加しますか?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"今日: <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"読み込み中..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>以降の予定を表示しています。タップするとそれ以外の予定を表示します。"</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>までの予定を表示しています。タップするとそれ以外の予定を表示します。"</string>
+    <string name="show_older_events" msgid="6819097859184398737">"<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>以降の予定を表示しています。それ以前の予定を検索する場合はここをタップしてください。"</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>までの予定を表示しています。それ以降の予定を検索する場合はここをタップしてください。"</string>
+    <string name="search_title" msgid="8898015280250716254">"マイカレンダーを検索"</string>
     <string name="num_events" msgid="3351672964607162257">"予定数"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"予定を編集"</string>
     <string name="delete_label" msgid="874742811918030739">"削除"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"予定を削除"</string>
     <string name="save_label" msgid="2133599104834432589">"完了"</string>
-    <string name="discard_label" msgid="4510607554910139220">"キャンセル"</string>
+    <string name="discard_label" msgid="2394623883289782541">"キャンセル"</string>
     <string name="import_label" msgid="6993796574741477222">"インポート"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"すべてスヌーズ"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"通知を消去"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"この予定を削除します。"</string>
     <string name="delete_title" msgid="5143743507524995383">"削除"</string>
     <string name="change_response_title" msgid="7704714776070974984">"出欠状況を変更"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"設定"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"カレンダーの表示設定"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"通知設定"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"通知設定"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"辞退した予定を非表示"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"通知方法"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"通知方法"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"週の開始"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"週の開始"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"検索履歴を消去"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"実行したすべての検索を削除"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"検索履歴が消去されました"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"通知"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"バイブレーション"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"通知時: バイブレーションON"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"着信音を選択"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"ポップアップ通知"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"デフォルトの通知時間"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"リマインダー通知"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"使用しない"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"バイブレーション"</string>
+    <string name="gadget_title" msgid="939918875331310945">"カレンダー"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"他1件"</item>
+    <item quantity="other" msgid="7993512762778431065">"他<xliff:g id="NUMBER">%d</xliff:g>件"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"予定されているカレンダーの予定はありません"</string>
+    <string name="tomorrow" msgid="614434811813859237">"明日"</string>
+    <string name="in_progress" msgid="3637799356103489011">"進行中"</string>
+    <string name="today" msgid="8577375020252453006">"今日"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"<xliff:g id="DOMAIN">%s</xliff:g>を検索中..."</string>
 </resources>
diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml
index 81da271..ab00996 100644
--- a/res/values-ko/arrays.xml
+++ b/res/values-ko/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2일"</item>
     <item msgid="3390146235648970198">"1주"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"없음"</item>
+    <item msgid="3377199512833209480">"1분"</item>
+    <item msgid="5023205715881387516">"5분"</item>
+    <item msgid="7720870540593319107">"10분"</item>
+    <item msgid="1653841700859180907">"15분"</item>
+    <item msgid="3741629746313107482">"20분"</item>
+    <item msgid="5357458446224446398">"25분"</item>
+    <item msgid="403143749684280703">"30분"</item>
+    <item msgid="5611574008997362948">"45분"</item>
+    <item msgid="30462793717552">"1시간"</item>
+    <item msgid="119808229352802480">"2시간"</item>
+    <item msgid="7456742278252094745">"3시간"</item>
+    <item msgid="5263928189935026559">"12시간"</item>
+    <item msgid="6451763179435252852">"24시간"</item>
+    <item msgid="966666856467721784">"2일"</item>
+    <item msgid="5840973497558794742">"1주"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"언어 기본값"</item>
+    <item msgid="134027225275475280">"토요일"</item>
+    <item msgid="95029346069903091">"일요일"</item>
+    <item msgid="5840983116375063739">"월요일"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"마셜 제도"</item>
     <item msgid="7176569939440505575">"미드웨이 제도"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"베오그라드"</item>
     <item msgid="3284290759549286849">"브뤼셀"</item>
     <item msgid="3112943938015033049">"사라예보"</item>
-    <item msgid="2184425710639965762">"파리"</item>
-    <item msgid="5997630882187580225">"빈트후크"</item>
-    <item msgid="3828800493609994208">"서아프리카 시간대"</item>
-    <item msgid="7527935722014187591">"암만, 요르단"</item>
-    <item msgid="672509109295227133">"아테네, 이스탄불"</item>
-    <item msgid="1835470340349221646">"베이루트, 레바논"</item>
-    <item msgid="9006223109403693650">"카이로"</item>
-    <item msgid="2877875673636361825">"헬싱키"</item>
-    <item msgid="4528124308590807049">"예루살렘"</item>
-    <item msgid="7823856457885290195">"민스크"</item>
-    <item msgid="8650995074282357197">"하라레"</item>
-    <item msgid="209523815326871285">"바그다드"</item>
-    <item msgid="8077211460729251609">"모스크바"</item>
-    <item msgid="6103383184232804522">"쿠웨이트"</item>
-    <item msgid="8581935265080466099">"나이로비"</item>
-    <item msgid="1531618843539764728">"테헤란"</item>
-    <item msgid="141548505188106312">"바쿠"</item>
-    <item msgid="5974315340250149692">"트빌리시"</item>
-    <item msgid="3344801680659888795">"예레반"</item>
-    <item msgid="2537944335416132075">"두바이"</item>
-    <item msgid="5630432498109110949">"카불"</item>
-    <item msgid="2274762525650993846">"이슬라마바드, 카라치"</item>
-    <item msgid="272153171103339242">"우랄스크"</item>
-    <item msgid="4757644551253886090">"에카테린부르크"</item>
-    <item msgid="3036512479614493957">"콜카타"</item>
-    <item msgid="4190271241277312277">"스리랑카"</item>
-    <item msgid="4489139826358657866">"카트만두"</item>
-    <item msgid="1195461607876034232">"아스타나"</item>
-    <item msgid="8777509327730950228">"양곤"</item>
-    <item msgid="3838899720161825998">"크라스노야르스크"</item>
-    <item msgid="6495532062164690437">"방콕"</item>
-    <item msgid="4910549870048384487">"베이징"</item>
-    <item msgid="7174954011332328153">"홍콩"</item>
-    <item msgid="415034182139280334">"이르쿠츠크"</item>
-    <item msgid="3407263784503834649">"쿠알라룸푸르"</item>
-    <item msgid="6547945295249807755">"퍼스"</item>
-    <item msgid="4571776847411430193">"타이베이"</item>
-    <item msgid="3043691515163887690">"서울"</item>
-    <item msgid="6369124969606372122">"도쿄, 오사카"</item>
-    <item msgid="8317842783685169193">"야쿠츠크"</item>
-    <item msgid="6993206457323990990">"애들레이드"</item>
-    <item msgid="2431970257749452312">"다윈"</item>
-    <item msgid="2709489188606506801">"브리즈번"</item>
-    <item msgid="6832511138206913044">"호바트"</item>
-    <item msgid="6933429790996727761">"시드니, 캔버라"</item>
-    <item msgid="4546625974877459074">"블라디보스토크"</item>
-    <item msgid="7421491250467489614">"괌"</item>
-    <item msgid="3450367738093440984">"마가단"</item>
-    <item msgid="5046540868552082971">"오클랜드"</item>
-    <item msgid="481903191189494072">"피지"</item>
-    <item msgid="1926932394586102998">"통가"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"없음"</item>
-    <item msgid="3377199512833209480">"1분"</item>
-    <item msgid="5023205715881387516">"5분"</item>
-    <item msgid="7720870540593319107">"10분"</item>
-    <item msgid="1653841700859180907">"15분"</item>
-    <item msgid="3741629746313107482">"20분"</item>
-    <item msgid="5357458446224446398">"25분"</item>
-    <item msgid="403143749684280703">"30분"</item>
-    <item msgid="5611574008997362948">"45분"</item>
-    <item msgid="30462793717552">"1시간"</item>
-    <item msgid="119808229352802480">"2시간"</item>
-    <item msgid="7456742278252094745">"3시간"</item>
-    <item msgid="5263928189935026559">"12시간"</item>
-    <item msgid="6451763179435252852">"24시간"</item>
-    <item msgid="966666856467721784">"2일"</item>
-    <item msgid="5840973497558794742">"1주"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"알리미 사용"</item>
-    <item msgid="8723674378160518790">"상태 표시줄 알림"</item>
-    <item msgid="8433736231022186661">"사용 안함"</item>
+    <item msgid="1746966478800108111">"빈트후크"</item>
+    <item msgid="7008598576103662879">"서아프리카"</item>
+    <item msgid="1358916275855835758">"암만, 요르단"</item>
+    <item msgid="5900185968201011015">"아테네, 이스탄불"</item>
+    <item msgid="4975304496845689895">"베이루트, 레바논"</item>
+    <item msgid="4099790960681254059">"카이로"</item>
+    <item msgid="4174072690298729061">"헬싱키"</item>
+    <item msgid="5233437715028921599">"예루살렘"</item>
+    <item msgid="612137838544847325">"민스크"</item>
+    <item msgid="385832033162858704">"하라레"</item>
+    <item msgid="1439592855284837653">"바그다드"</item>
+    <item msgid="3703352987270492256">"모스크바"</item>
+    <item msgid="1657996636394546903">"쿠웨이트"</item>
+    <item msgid="3719675800096223903">"나이로비"</item>
+    <item msgid="4216389209911081345">"테헤란"</item>
+    <item msgid="4949871918709805735">"바쿠"</item>
+    <item msgid="2863183321532922311">"트빌리시"</item>
+    <item msgid="1943529325618264999">"예레반"</item>
+    <item msgid="5094507376721687886">"두바이"</item>
+    <item msgid="2849654571454095226">"카불"</item>
+    <item msgid="3641915655045056824">"이슬라마바드/카라치"</item>
+    <item msgid="5795351343828254400">"우랄스크"</item>
+    <item msgid="8779467771115356898">"에카테린부르크"</item>
+    <item msgid="297412491658718331">"콜카타"</item>
+    <item msgid="2290076787008042202">"스리랑카"</item>
+    <item msgid="4119703758417825749">"카트만두"</item>
+    <item msgid="9161760299912900568">"아스타나"</item>
+    <item msgid="5753596907402377327">"양곤"</item>
+    <item msgid="6275146302340764856">"크라스노야르스크"</item>
+    <item msgid="2385588555356900033">"방콕"</item>
+    <item msgid="7651147941145227340">"베이징"</item>
+    <item msgid="7831696303531874014">"홍콩"</item>
+    <item msgid="7943498021949728203">"이르쿠츠크"</item>
+    <item msgid="707009198021550118">"콸라룸푸르"</item>
+    <item msgid="4268864549735629220">"퍼스"</item>
+    <item msgid="4584239166170514795">"타이베이"</item>
+    <item msgid="4972387553080120086">"서울"</item>
+    <item msgid="5749349714875345358">"도쿄, 오사카"</item>
+    <item msgid="4435870687083770252">"야쿠츠크"</item>
+    <item msgid="4857952326324510625">"아델라이데"</item>
+    <item msgid="7802545626741434274">"다윈"</item>
+    <item msgid="7184691104734480971">"브리스번"</item>
+    <item msgid="7795651165061255022">"호바트"</item>
+    <item msgid="1858052331367859514">"시드니, 캔버라"</item>
+    <item msgid="4396794682654884440">"블라디보스토크"</item>
+    <item msgid="4152655496917506653">"괌"</item>
+    <item msgid="3608508135351982746">"마가단"</item>
+    <item msgid="722180563708274099">"오클랜드"</item>
+    <item msgid="1736037231626139044">"피지"</item>
+    <item msgid="8187746821081424875">"통가"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"바쁨"</item>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index dfe1124..13add2f 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"캘린더"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"캘린더 올인원"</string>
     <string name="what_label" msgid="1933578391985810049">"내용"</string>
     <string name="where_label" msgid="3472482206065595118">"장소"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"시간대"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"시간대"</string>
     <string name="attendees_label" msgid="2971281641565729725">"참석자"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g>(<xliff:g id="GUEST_COUNT">%d</xliff:g>개)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"반복"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1일"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g>일"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"새로고침"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"일정목록 표시"</string>
     <string name="show_day_view" msgid="5782512782605466016">"요일 표시"</string>
     <string name="agenda_view" msgid="5566084759423843245">"일정목록"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"오늘"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"캘린더"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"설정"</string>
+    <string name="search" msgid="550656004486017426">"검색"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(<xliff:g id="MORE_COUNT">%d</xliff:g>개 일정 더보기...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"캘린더"</string>
     <string name="synced_visible" msgid="1406744353749335651">"동기화 됨, 표시됨"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"동기화 됨, 표시되지 않음"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"동기화 안 됨, 표시되지 않음"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"일정 세부정보"</string>
     <string name="hint_what" msgid="709155115005044531">"일정 이름"</string>
     <string name="hint_where" msgid="3116239630502213241">"일정 장소"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"캘린더"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"나머지 옵션 표시"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"나머지 옵션 숨기기"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"모두 표시 &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"모두 표시"</string>
     <string name="description_label" msgid="7193475046717505855">"설명"</string>
     <string name="presence_label" msgid="6169724148441175862">"내 상태"</string>
     <string name="privacy_label" msgid="9215031965259597335">"공개여부"</string>
     <string name="reminders_label" msgid="8345054160145333166">"알림"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"캘린더 없음"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"캘린더가 없습니다."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"시작 날짜"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"시작 시간"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"종료 날짜"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"종료 시간"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"알림"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"알림 삭제"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"캘린더:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"주최자:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"현지 시간대:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"참석하시겠습니까?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"오늘, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"로드 중..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> 이후의 일정입니다. 더 보려면 누르세요."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>까지의 일정입니다. 더 보려면 누르세요."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> 이후 일정 표시. 이전 일정을 검색하려면 터치합니다."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>까지의 일정 표시. 이후 일정을 검색하려면 터치합니다."</string>
+    <string name="search_title" msgid="8898015280250716254">"내 캘린더 검색"</string>
     <string name="num_events" msgid="3351672964607162257">"일정 개수"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"일정 수정"</string>
     <string name="delete_label" msgid="874742811918030739">"삭제"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"일정 삭제"</string>
     <string name="save_label" msgid="2133599104834432589">"완료"</string>
-    <string name="discard_label" msgid="4510607554910139220">"취소"</string>
+    <string name="discard_label" msgid="2394623883289782541">"취소"</string>
     <string name="import_label" msgid="6993796574741477222">"가져오기"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"모든 알람 일시 중지"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"모두 해제"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"일정이 삭제됩니다."</string>
     <string name="delete_title" msgid="5143743507524995383">"삭제"</string>
     <string name="change_response_title" msgid="7704714776070974984">"회신 변경"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"설정"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"캘린더 보기 설정"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"알림 설정"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"알림 설정"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"거부한 일정 숨기기"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"알림 설정"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"알림 설정"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"한 주의 시작"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"한 주의 시작"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"검색기록 지우기"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"수행한 모든 검색 삭제"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"검색기록이 지워짐"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"알림"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"진동"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"진동 알림"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"알림음 선택"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"팝업 알림"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"기본 알림 시간"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"기본 알림 시간"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"사용 안함"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"진동"</string>
+    <string name="gadget_title" msgid="939918875331310945">"캘린더"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"1개 일정 더보기"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g>개 일정 더보기"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"예정된 캘린더 일정이 없습니다."</string>
+    <string name="tomorrow" msgid="614434811813859237">"내일"</string>
+    <string name="in_progress" msgid="3637799356103489011">"진행 중"</string>
+    <string name="today" msgid="8577375020252453006">"오늘"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"<xliff:g id="DOMAIN">%s</xliff:g> 검색 중..."</string>
 </resources>
diff --git a/res/values-land/integers.xml b/res/values-land/dimens.xml
similarity index 80%
rename from res/values-land/integers.xml
rename to res/values-land/dimens.xml
index 9ece108..2cdfb99 100644
--- a/res/values-land/integers.xml
+++ b/res/values-land/dimens.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2009 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.
@@ -15,5 +15,6 @@
 -->
 
 <resources>
-    <integer name="number_of_hours">8</integer>
+    <dimen name="appwidget_width">206dip</dimen>
+    <dimen name="appwidget_height">143dip</dimen>
 </resources>
diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml
index 16f0774..67efe56 100644
--- a/res/values-nb/arrays.xml
+++ b/res/values-nb/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dager"</item>
     <item msgid="3390146235648970198">"1 uke"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Ingen"</item>
+    <item msgid="3377199512833209480">"1 minutt"</item>
+    <item msgid="5023205715881387516">"5 minutter"</item>
+    <item msgid="7720870540593319107">"10 minutter"</item>
+    <item msgid="1653841700859180907">"15 minutter"</item>
+    <item msgid="3741629746313107482">"20 minutter"</item>
+    <item msgid="5357458446224446398">"25 minutter"</item>
+    <item msgid="403143749684280703">"30 minutter"</item>
+    <item msgid="5611574008997362948">"45 minutter"</item>
+    <item msgid="30462793717552">"1 time"</item>
+    <item msgid="119808229352802480">"2 timer"</item>
+    <item msgid="7456742278252094745">"3 timer"</item>
+    <item msgid="5263928189935026559">"12 timer"</item>
+    <item msgid="6451763179435252852">"24 timer"</item>
+    <item msgid="966666856467721784">"2 dager"</item>
+    <item msgid="5840973497558794742">"1 uke"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Språkstandard"</item>
+    <item msgid="134027225275475280">"Lørdag"</item>
+    <item msgid="95029346069903091">"Søndag"</item>
+    <item msgid="5840983116375063739">"Mandag"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshalløyene"</item>
     <item msgid="7176569939440505575">"Midway Island"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Beograd"</item>
     <item msgid="3284290759549286849">"Brussel"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Vestafrika-tid"</item>
-    <item msgid="7527935722014187591">"Amman, Jordan"</item>
-    <item msgid="672509109295227133">"Aten, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beirut, Libanon"</item>
-    <item msgid="9006223109403693650">"Kairo"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerusalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskva"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Jerevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Jekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Kathmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnojarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Beijing"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seoul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Ingen"</item>
-    <item msgid="3377199512833209480">"1 minutt"</item>
-    <item msgid="5023205715881387516">"5 minutter"</item>
-    <item msgid="7720870540593319107">"10 minutter"</item>
-    <item msgid="1653841700859180907">"15 minutter"</item>
-    <item msgid="3741629746313107482">"20 minutter"</item>
-    <item msgid="5357458446224446398">"25 minutter"</item>
-    <item msgid="403143749684280703">"30 minutter"</item>
-    <item msgid="5611574008997362948">"45 minutter"</item>
-    <item msgid="30462793717552">"1 time"</item>
-    <item msgid="119808229352802480">"2 timer"</item>
-    <item msgid="7456742278252094745">"3 timer"</item>
-    <item msgid="5263928189935026559">"12 timer"</item>
-    <item msgid="6451763179435252852">"24 timer"</item>
-    <item msgid="966666856467721784">"2 dager"</item>
-    <item msgid="5840973497558794742">"1 uke"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Advarsel"</item>
-    <item msgid="8723674378160518790">"Varsling i statusfeltet"</item>
-    <item msgid="8433736231022186661">"Av"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Vestafrika-tid"</item>
+    <item msgid="1358916275855835758">"Amman, Jordan"</item>
+    <item msgid="5900185968201011015">"Aten, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beirut, Libanon"</item>
+    <item msgid="4099790960681254059">"Kairo"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerusalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskva"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Jerevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamab.,Karachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Jekaterinburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnojarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Beijing"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seoul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Opptatt"</item>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 7ed4fab..b420e38 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalender"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"AllInOne-kalender"</string>
     <string name="what_label" msgid="1933578391985810049">"Hva"</string>
     <string name="where_label" msgid="3472482206065595118">"Hvor"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Tidssone"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Tidssone"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Gjester"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Gjentas"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"En dag"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dager"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Last inn"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Vis agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Vis dag"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"I dag"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalendere"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Innstillinger"</string>
+    <string name="search" msgid="550656004486017426">"Søk"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(samt <xliff:g id="MORE_COUNT">%d</xliff:g> til …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalendere"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synkronisert og synlig"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synkronisert og ikke synlig"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"ikke synkronisert og ikke synlig"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Hendelsesdetaljer"</string>
     <string name="hint_what" msgid="709155115005044531">"Navn på aktivitet"</string>
     <string name="hint_where" msgid="3116239630502213241">"Sted"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalender"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Vis ekstra valg"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Skjul ekstra valg"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Vis alle"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Vis alle"</string>
     <string name="description_label" msgid="7193475046717505855">"Beskrivelse"</string>
     <string name="presence_label" msgid="6169724148441175862">"Vis meg som"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Personvern"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Påminnelser"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Ingen kalendre"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Du har ingen kalendre."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Fra dato"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Fra klokkeslett"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Til dato"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Til klokkeslett"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Påminnelser"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Fjern påminnelse"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalender:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Arrangør:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokal tidssone:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Kommer du?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"I dag, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Laster inn ..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Viser aktiviteter siden <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Trykk for å se etter flere."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Viser aktiviteter inntil <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Trykk for å se etter flere."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Viser aktiviteter etter <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Trykk for å søke etter tidligere aktiviteter."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Viser aktiviteter inntil <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Trykk for å søke etter senere aktiviteter."</string>
+    <string name="search_title" msgid="8898015280250716254">"Søk i mine kalendere"</string>
     <string name="num_events" msgid="3351672964607162257">"Ant. aktiviteter"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Rediger aktivitet"</string>
     <string name="delete_label" msgid="874742811918030739">"Slett"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Slett aktivitet"</string>
     <string name="save_label" msgid="2133599104834432589">"OK"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Tilbakestill"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Avbryt"</string>
     <string name="import_label" msgid="6993796574741477222">"Importér"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Slumre alle"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Skjul alle"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Denne aktiviteten vil bli slettet."</string>
     <string name="delete_title" msgid="5143743507524995383">"Slett"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Endre svar"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Innstillinger"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Kalender visningsinnstilling"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Innstillinger for påminnelser"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Påminnelsesinnstillinger"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Skjul avslåtte aktiviteter"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Sett varsling og påminnelser"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Varsling og påminnelser"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Uke starter på"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Uke starter på"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Tøm søkeloggen"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Fjern alle utførte søk"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Søkelogg slettet"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Varsler"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrering"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Også vibrering for påminnelser"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Velg ringetone"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Forgrunnsvinduvarsel"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Sett standard påminnelse"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Standard påminnelsestid"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Aldri"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrering"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalender"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"En hendelse til"</item>
+    <item quantity="other" msgid="7993512762778431065">"<xliff:g id="NUMBER">%d</xliff:g> hendelser til"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Ingen kommende kalenderhendelser"</string>
+    <string name="tomorrow" msgid="614434811813859237">"I morgen"</string>
+    <string name="in_progress" msgid="3637799356103489011">"pågår"</string>
+    <string name="today" msgid="8577375020252453006">"I dag"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Søker på <xliff:g id="DOMAIN">%s</xliff:g> …"</string>
 </resources>
diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml
index 496d369..49f69fa 100644
--- a/res/values-nl/arrays.xml
+++ b/res/values-nl/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dagen"</item>
     <item msgid="3390146235648970198">"1 week"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Geen"</item>
+    <item msgid="3377199512833209480">"1 minuut"</item>
+    <item msgid="5023205715881387516">"5 minuten"</item>
+    <item msgid="7720870540593319107">"10 minuten"</item>
+    <item msgid="1653841700859180907">"15 minuten"</item>
+    <item msgid="3741629746313107482">"20 minuten"</item>
+    <item msgid="5357458446224446398">"25 minuten"</item>
+    <item msgid="403143749684280703">"30 minuten"</item>
+    <item msgid="5611574008997362948">"45 minuten"</item>
+    <item msgid="30462793717552">"1 uur"</item>
+    <item msgid="119808229352802480">"2 uur"</item>
+    <item msgid="7456742278252094745">"3 uur"</item>
+    <item msgid="5263928189935026559">"12 uur"</item>
+    <item msgid="6451763179435252852">"24 uur"</item>
+    <item msgid="966666856467721784">"2 dagen"</item>
+    <item msgid="5840973497558794742">"1 week"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Standaardlandinstelling"</item>
+    <item msgid="134027225275475280">"Zaterdag"</item>
+    <item msgid="95029346069903091">"Zondag"</item>
+    <item msgid="5840983116375063739">"Maandag"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshalleilanden"</item>
     <item msgid="7176569939440505575">"Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Brussel"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Parijs"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"W. Africa Time"</item>
-    <item msgid="7527935722014187591">"Amman, Jordanië"</item>
-    <item msgid="672509109295227133">"Athene, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beiroet, Libanon"</item>
-    <item msgid="9006223109403693650">"Caïro"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jeruzalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskou"</item>
-    <item msgid="6103383184232804522">"Koeweit"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Jerevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Oeralsk"</item>
-    <item msgid="4757644551253886090">"Jekaterinenburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Kathmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Beijing"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkoetsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seoul"</item>
-    <item msgid="6369124969606372122">"Tokio, Osaka"</item>
-    <item msgid="8317842783685169193">"Jakoetsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Geen"</item>
-    <item msgid="3377199512833209480">"1 minuut"</item>
-    <item msgid="5023205715881387516">"5 minuten"</item>
-    <item msgid="7720870540593319107">"10 minuten"</item>
-    <item msgid="1653841700859180907">"15 minuten"</item>
-    <item msgid="3741629746313107482">"20 minuten"</item>
-    <item msgid="5357458446224446398">"25 minuten"</item>
-    <item msgid="403143749684280703">"30 minuten"</item>
-    <item msgid="5611574008997362948">"45 minuten"</item>
-    <item msgid="30462793717552">"1 uur"</item>
-    <item msgid="119808229352802480">"2 uur"</item>
-    <item msgid="7456742278252094745">"3 uur"</item>
-    <item msgid="5263928189935026559">"12 uur"</item>
-    <item msgid="6451763179435252852">"24 uur"</item>
-    <item msgid="966666856467721784">"2 dagen"</item>
-    <item msgid="5840973497558794742">"1 week"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Melding"</item>
-    <item msgid="8723674378160518790">"Statusbalkmelding"</item>
-    <item msgid="8433736231022186661">"Uit"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"W. Africa Time"</item>
+    <item msgid="1358916275855835758">"Amman, Jordanië"</item>
+    <item msgid="5900185968201011015">"Athene, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beiroet, Libanon"</item>
+    <item msgid="4099790960681254059">"Caïro"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jeruzalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskou"</item>
+    <item msgid="1657996636394546903">"Koeweit"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Jerevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Oeralsk"</item>
+    <item msgid="8779467771115356898">"Jekaterinenburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Beijing"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkoetsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seoul"</item>
+    <item msgid="5749349714875345358">"Tokio, Osaka"</item>
+    <item msgid="4435870687083770252">"Jakoetsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Bezet"</item>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index ca32a99..5e4a955 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Agenda"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Agenda AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Wat"</string>
     <string name="where_label" msgid="3472482206065595118">"Waar"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Tijdzone"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Tijdzone"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Gasten"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Herhaling"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dag"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dagen"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Vernieuwen"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Geef agenda weer"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Geef dag weer"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Vandaag"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Agenda\'s"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Instellingen"</string>
+    <string name="search" msgid="550656004486017426">"Zoeken"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(plus <xliff:g id="MORE_COUNT">%d</xliff:g> extra …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Agenda\'s"</string>
     <string name="synced_visible" msgid="1406744353749335651">"gesynchroniseerd, zichtbaar"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"gesynchroniseerd, niet zichtbaar"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"niet gesynchroniseerd, niet zichtbaar"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Afspraakgegevens"</string>
     <string name="hint_what" msgid="709155115005044531">"Afspraaknaam"</string>
     <string name="hint_where" msgid="3116239630502213241">"Locatie van de afspraak"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Agenda"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Extra opties weergeven"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Extra opties verbergen"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Alles weergeven &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Alles weergeven"</string>
     <string name="description_label" msgid="7193475046717505855">"Beschrijving"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mij weergeven als"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacy"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Herinneringen"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Geen agenda\'s"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"U heeft geen agenda\'s."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Begindatum"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Begintijd"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Einddatum"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Eindtijd"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Herinneringen"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Herinnering verwijderen"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Agenda:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organisator:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokale tijdzone:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Aanwezig?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Vandaag, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Laden..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Afspraken weergeven sinds <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Tik om er meer te zoeken."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Afspraken weergeven tot <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Tik om er meer te zoeken."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Gebeurtenissen na <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Raak aan om eerdere gebeurtenissen te zoeken."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Gebeurtenissen tot <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Raak aan om latere gebeurtenissen te zoeken."</string>
+    <string name="search_title" msgid="8898015280250716254">"Zoeken in mijn agenda\'s"</string>
     <string name="num_events" msgid="3351672964607162257">"Aantal afspraken"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Afspraak bewerken"</string>
     <string name="delete_label" msgid="874742811918030739">"Verwijderen"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Afspraak verwijderen"</string>
     <string name="save_label" msgid="2133599104834432589">"Gereed"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Terugzetten"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Annuleren"</string>
     <string name="import_label" msgid="6993796574741477222">"Importeren"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"5 min. uitstellen"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Allemaal verwijderen"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Deze afspraak wordt verwijderd."</string>
     <string name="delete_title" msgid="5143743507524995383">"Verwijderen"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Reactie wijzigen"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Instellingen"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Instelling agendaweergave"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Instellingen voor herinneringen"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Instellingen voor herinneringen"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Geweigerde afspraken verbergen"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Meldingen instellen"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Meldingen en berichten"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Week begint op"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Week begint op"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Zoekgeschiedenis wissen"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Alle uitgevoerde zoekopdrachten verwijderen"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Zoekgeschiedenis gewist"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Meldingen"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Trillen"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Ook trillen voor herinneringen"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Beltoon selecteren"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Pop-upmelding"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Herinneringstijd"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Herinneringstijd"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nooit"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Trillen"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Agenda"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"nog 1 gebeurtenis"</item>
+    <item quantity="other" msgid="7993512762778431065">"nog <xliff:g id="NUMBER">%d</xliff:g> gebeurtenissen"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Geen geplande afspraken"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Morgen"</string>
+    <string name="in_progress" msgid="3637799356103489011">"in behandeling"</string>
+    <string name="today" msgid="8577375020252453006">"Vandaag"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"<xliff:g id="DOMAIN">%s</xliff:g> zoeken…"</string>
 </resources>
diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml
index 88b1446..6961cc1 100644
--- a/res/values-pl/arrays.xml
+++ b/res/values-pl/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dni"</item>
     <item msgid="3390146235648970198">"1 tydzień"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Brak"</item>
+    <item msgid="3377199512833209480">"1 minuta"</item>
+    <item msgid="5023205715881387516">"5 minut"</item>
+    <item msgid="7720870540593319107">"10 minut"</item>
+    <item msgid="1653841700859180907">"15 minut"</item>
+    <item msgid="3741629746313107482">"20 minut"</item>
+    <item msgid="5357458446224446398">"25 minut"</item>
+    <item msgid="403143749684280703">"30 minut"</item>
+    <item msgid="5611574008997362948">"45 minut"</item>
+    <item msgid="30462793717552">"1 godzina"</item>
+    <item msgid="119808229352802480">"2 godziny"</item>
+    <item msgid="7456742278252094745">"3 godziny"</item>
+    <item msgid="5263928189935026559">"12 godzin"</item>
+    <item msgid="6451763179435252852">"24 godziny"</item>
+    <item msgid="966666856467721784">"2 dni"</item>
+    <item msgid="5840973497558794742">"1 tydzień"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Domyślny dla regionu"</item>
+    <item msgid="134027225275475280">"Sobota"</item>
+    <item msgid="95029346069903091">"Niedziela"</item>
+    <item msgid="5840983116375063739">"Poniedziałek"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Wyspy Marshalla"</item>
     <item msgid="7176569939440505575">"Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrad"</item>
     <item msgid="3284290759549286849">"Bruksela"</item>
     <item msgid="3112943938015033049">"Sarajewo"</item>
-    <item msgid="2184425710639965762">"Paryż"</item>
-    <item msgid="5997630882187580225">"Windhuk"</item>
-    <item msgid="3828800493609994208">"Czas zachodnioafrykański"</item>
-    <item msgid="7527935722014187591">"Amman, Jordania"</item>
-    <item msgid="672509109295227133">"Ateny, Stambuł"</item>
-    <item msgid="1835470340349221646">"Bejrut, Liban"</item>
-    <item msgid="9006223109403693650">"Kair"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Jerozolima"</item>
-    <item msgid="7823856457885290195">"Mińsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskwa"</item>
-    <item msgid="6103383184232804522">"Kuwejt"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Erywań"</item>
-    <item msgid="2537944335416132075">"Dubaj"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karaczi"</item>
-    <item msgid="272153171103339242">"Orał"</item>
-    <item msgid="4757644551253886090">"Jekaterynburg"</item>
-    <item msgid="3036512479614493957">"Kalkuta"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Rangun"</item>
-    <item msgid="3838899720161825998">"Krasnojarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Pekin"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkuck"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Tajpej"</item>
-    <item msgid="3043691515163887690">"Seul"</item>
-    <item msgid="6369124969606372122">"Tokio, Osaka"</item>
-    <item msgid="8317842783685169193">"Jakuck"</item>
-    <item msgid="6993206457323990990">"Adelajda"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Władywostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fidżi"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Brak"</item>
-    <item msgid="3377199512833209480">"1 minuta"</item>
-    <item msgid="5023205715881387516">"5 minut"</item>
-    <item msgid="7720870540593319107">"10 minut"</item>
-    <item msgid="1653841700859180907">"15 minut"</item>
-    <item msgid="3741629746313107482">"20 minut"</item>
-    <item msgid="5357458446224446398">"25 minut"</item>
-    <item msgid="403143749684280703">"30 minut"</item>
-    <item msgid="5611574008997362948">"45 minut"</item>
-    <item msgid="30462793717552">"1 godzina"</item>
-    <item msgid="119808229352802480">"2 godziny"</item>
-    <item msgid="7456742278252094745">"3 godziny"</item>
-    <item msgid="5263928189935026559">"12 godzin"</item>
-    <item msgid="6451763179435252852">"24 godziny"</item>
-    <item msgid="966666856467721784">"2 dni"</item>
-    <item msgid="5840973497558794742">"1 tydzień"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alarm"</item>
-    <item msgid="8723674378160518790">"Powiadomienie na pasku stanu"</item>
-    <item msgid="8433736231022186661">"Wyłączone"</item>
+    <item msgid="1746966478800108111">"Windhuk"</item>
+    <item msgid="7008598576103662879">"Czas zachodnioafrykański"</item>
+    <item msgid="1358916275855835758">"Amman, Jordania"</item>
+    <item msgid="5900185968201011015">"Ateny, Stambuł"</item>
+    <item msgid="4975304496845689895">"Bejrut, Liban"</item>
+    <item msgid="4099790960681254059">"Kair"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Jerozolima"</item>
+    <item msgid="612137838544847325">"Mińsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskwa"</item>
+    <item msgid="1657996636394546903">"Kuwejt"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Erywań"</item>
+    <item msgid="5094507376721687886">"Dubaj"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karaczi"</item>
+    <item msgid="5795351343828254400">"Orał"</item>
+    <item msgid="8779467771115356898">"Jekaterynburg"</item>
+    <item msgid="297412491658718331">"Kalkuta"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Rangun"</item>
+    <item msgid="6275146302340764856">"Krasnojarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Pekin"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkuck"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Tajpej"</item>
+    <item msgid="4972387553080120086">"Seul"</item>
+    <item msgid="5749349714875345358">"Tokio, Osaka"</item>
+    <item msgid="4435870687083770252">"Jakuck"</item>
+    <item msgid="4857952326324510625">"Adelajda"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Władywostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fidżi"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Zajęty"</item>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b658397..38c0d03 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalendarz"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Kalendarz pełny"</string>
     <string name="what_label" msgid="1933578391985810049">"Co"</string>
     <string name="where_label" msgid="3472482206065595118">"Gdzie"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Strefa czasowa"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Strefa czasowa"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Goście"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Powtórzenie"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dzień"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dni"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Odśwież"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Pokaż plan dnia"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Pokaż dzień"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Plan dnia"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Dzisiaj"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalendarze"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Ustawienia"</string>
+    <string name="search" msgid="550656004486017426">"Szukaj"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(i <xliff:g id="MORE_COUNT">%d</xliff:g> więcej…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalendarze"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synchronizowany, widoczny"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synchronizowany, niewidoczny"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"bez synchronizacji, niewidoczny"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Szczegóły wydarzenia"</string>
     <string name="hint_what" msgid="709155115005044531">"Nazwa wydarzenia"</string>
     <string name="hint_where" msgid="3116239630502213241">"Lokalizacja wydarzenia"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalendarz"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Pokaż dodatkowe opcje"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ukryj dodatkowe opcje"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Pokaż wszystkie &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Pokaż wszystkie"</string>
     <string name="description_label" msgid="7193475046717505855">"Opis"</string>
     <string name="presence_label" msgid="6169724148441175862">"Pokaż mój stan jako"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Prywatność"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Przypomnienia"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Brak kalendarzy"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Nie masz kalendarzy."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Od daty"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Od godziny"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Do daty"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Do godziny"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Przypomnienia"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Usuń przypomnienie"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalendarz:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizator:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokalna strefa czasowa:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Uczestnictwo"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Dzisiaj, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Trwa wczytywanie..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Pokazuje wydarzenia od <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Dotknij, aby wyszukać więcej."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Pokazuje wydarzenia do <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Dotknij, aby wyszukać więcej."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Wyświetlanie wydarzeń od dnia <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Dotknij, aby wyszukać wcześniejsze wydarzenia."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Wyświetlanie wydarzeń do dnia <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Dotknij, aby wyszukać późniejsze wydarzenia."</string>
+    <string name="search_title" msgid="8898015280250716254">"Szukaj w moich kalendarzach"</string>
     <string name="num_events" msgid="3351672964607162257">"Liczba wydarzeń"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Edytuj wydarzenie"</string>
     <string name="delete_label" msgid="874742811918030739">"Usuń"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Usuń wydarzenie"</string>
     <string name="save_label" msgid="2133599104834432589">"Gotowe"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Przywróć"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Anuluj"</string>
     <string name="import_label" msgid="6993796574741477222">"Importuj"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Odłóż wszystkie"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Zamknij wszystkie"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"To wydarzenie zostanie usunięte."</string>
     <string name="delete_title" msgid="5143743507524995383">"Usuń"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Zmień odpowiedź"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Ustawienia"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Widok kalendarza"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Ustawienia przypomnień"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Ustawienia przypomnień"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Ukryj odrzucone"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Alerty i powiadomienia"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alerty i powiadomienia"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Początek tygodnia to"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Początek tygodnia to"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Wyczyść historię wyszukiwania"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Usuń wszystkie przeprowadzone wyszukiwania"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Wyczyszczono historię wyszukiwania"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Powiadomienia"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Wibracje"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Wibracje również dla przypomnień"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Wybierz dzwonek"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Wyskakujące powiadomienie"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Domyślne przypomnienie"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Domyślne przypomnienie"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nigdy"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Wibracje"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalendarz"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"Więcej wydarzeń: 1"</item>
+    <item quantity="other" msgid="7993512762778431065">"Więcej wydarzeń: <xliff:g id="NUMBER">%d</xliff:g>"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Brak zbliżających się wydarzeń w kalendarzu"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Jutro"</string>
+    <string name="in_progress" msgid="3637799356103489011">"w toku"</string>
+    <string name="today" msgid="8577375020252453006">"Dzisiaj"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Trwa wyszukiwanie w witrynie <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml
index 3a2ad1a..26c8314 100644
--- a/res/values-pt-rPT/arrays.xml
+++ b/res/values-pt-rPT/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dias"</item>
     <item msgid="3390146235648970198">"1 semana"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Nenhum"</item>
+    <item msgid="3377199512833209480">"1 minuto"</item>
+    <item msgid="5023205715881387516">"5 minutos"</item>
+    <item msgid="7720870540593319107">"10 minutos"</item>
+    <item msgid="1653841700859180907">"15 minutos"</item>
+    <item msgid="3741629746313107482">"20 minutos"</item>
+    <item msgid="5357458446224446398">"25 minutos"</item>
+    <item msgid="403143749684280703">"30 minutos"</item>
+    <item msgid="5611574008997362948">"45 minutos"</item>
+    <item msgid="30462793717552">"1 hora"</item>
+    <item msgid="119808229352802480">"2 horas"</item>
+    <item msgid="7456742278252094745">"3 horas"</item>
+    <item msgid="5263928189935026559">"12 horas"</item>
+    <item msgid="6451763179435252852">"24 horas"</item>
+    <item msgid="966666856467721784">"2 dias"</item>
+    <item msgid="5840973497558794742">"1 semana"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Local predefinido"</item>
+    <item msgid="134027225275475280">"Sábado"</item>
+    <item msgid="95029346069903091">"Domingo"</item>
+    <item msgid="5840983116375063739">"Segunda-feira"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Ilhas Marshall"</item>
     <item msgid="7176569939440505575">"Ilha de Midway"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Bruxelas"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Hora África Oc."</item>
-    <item msgid="7527935722014187591">"Amã, Jordânia"</item>
-    <item msgid="672509109295227133">"Atenas, Istambul"</item>
-    <item msgid="1835470340349221646">"Beirute, Líbano"</item>
-    <item msgid="9006223109403693650">"Cairo"</item>
-    <item msgid="2877875673636361825">"Helsínquia"</item>
-    <item msgid="4528124308590807049">"Jerusalém"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdade"</item>
-    <item msgid="8077211460729251609">"Moscovo"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teerão"</item>
-    <item msgid="141548505188106312">"Bacu"</item>
-    <item msgid="5974315340250149692">"Tiblissi"</item>
-    <item msgid="3344801680659888795">"Erevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Cabul"</item>
-    <item msgid="2274762525650993846">"Islamabade, Carachi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Ecatarimburgo"</item>
-    <item msgid="3036512479614493957">"Calcutá"</item>
-    <item msgid="4190271241277312277">"Sri Lanca"</item>
-    <item msgid="4489139826358657866">"Catmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Banguecoque"</item>
-    <item msgid="4910549870048384487">"Pequim"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipé"</item>
-    <item msgid="3043691515163887690">"Seul"</item>
-    <item msgid="6369124969606372122">"Tóquio, Osaca"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Camberra"</item>
-    <item msgid="4546625974877459074">"Vladivostoque"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Nenhum"</item>
-    <item msgid="3377199512833209480">"1 minuto"</item>
-    <item msgid="5023205715881387516">"5 minutos"</item>
-    <item msgid="7720870540593319107">"10 minutos"</item>
-    <item msgid="1653841700859180907">"15 minutos"</item>
-    <item msgid="3741629746313107482">"20 minutos"</item>
-    <item msgid="5357458446224446398">"25 minutos"</item>
-    <item msgid="403143749684280703">"30 minutos"</item>
-    <item msgid="5611574008997362948">"45 minutos"</item>
-    <item msgid="30462793717552">"1 hora"</item>
-    <item msgid="119808229352802480">"2 horas"</item>
-    <item msgid="7456742278252094745">"3 horas"</item>
-    <item msgid="5263928189935026559">"12 horas"</item>
-    <item msgid="6451763179435252852">"24 horas"</item>
-    <item msgid="966666856467721784">"2 dias"</item>
-    <item msgid="5840973497558794742">"1 semana"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alerta"</item>
-    <item msgid="8723674378160518790">"Notificação na barra de estado"</item>
-    <item msgid="8433736231022186661">"Desactivado"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Hora África Oc."</item>
+    <item msgid="1358916275855835758">"Amã, Jordânia"</item>
+    <item msgid="5900185968201011015">"Atenas, Istambul"</item>
+    <item msgid="4975304496845689895">"Beirute, Líbano"</item>
+    <item msgid="4099790960681254059">"Cairo"</item>
+    <item msgid="4174072690298729061">"Helsínquia"</item>
+    <item msgid="5233437715028921599">"Jerusalém"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdade"</item>
+    <item msgid="3703352987270492256">"Moscovo"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teerão"</item>
+    <item msgid="4949871918709805735">"Bacu"</item>
+    <item msgid="2863183321532922311">"Tiblissi"</item>
+    <item msgid="1943529325618264999">"Erevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Cabul"</item>
+    <item msgid="3641915655045056824">"Islamabade, Carachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Ecatarimburgo"</item>
+    <item msgid="297412491658718331">"Calcutá"</item>
+    <item msgid="2290076787008042202">"Sri Lanca"</item>
+    <item msgid="4119703758417825749">"Catmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Banguecoque"</item>
+    <item msgid="7651147941145227340">"Pequim"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipé"</item>
+    <item msgid="4972387553080120086">"Seul"</item>
+    <item msgid="5749349714875345358">"Tóquio, Osaca"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Camberra"</item>
+    <item msgid="4396794682654884440">"Vladivostoque"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Ocupado"</item>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6c039ac..8aed680 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Calendário"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Calendário AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"O quê"</string>
     <string name="where_label" msgid="3472482206065595118">"Onde"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Fuso horário"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Fuso horário"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Convidados"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Repetição"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dia"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dias"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Actualizar"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Mostrar agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Mostrar dia"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Hoje"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Calendários"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Definições"</string>
+    <string name="search" msgid="550656004486017426">"Pesquisar"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(mais <xliff:g id="MORE_COUNT">%d</xliff:g>...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Calendários"</string>
     <string name="synced_visible" msgid="1406744353749335651">"sincronizado, visível"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"sincronizado, não visível"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"não sincronizado, não visível"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Detalhes do evento"</string>
     <string name="hint_what" msgid="709155115005044531">"Nome do evento"</string>
     <string name="hint_where" msgid="3116239630502213241">"Localização do evento"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Calendário"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Mostrar opções extra"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ocultar opções extra"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Mostrar tudo &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Mostrar tudo"</string>
     <string name="description_label" msgid="7193475046717505855">"Descrição"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mostrar-me como"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacidade"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Lembretes"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Sem calendários"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Não tem calendários."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Data de início"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Hora de início"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Data de fim"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Hora de fim"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Lembretes"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Remover lembrete"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Calendário:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizador:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Fuso horário local:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Assistir?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Hoje, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"A carregar..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"A mostrar eventos desde <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para procurar mais."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"A mostrar eventos até <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para procurar mais."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"A mostrar eventos desde <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para pesquisar eventos anteriores."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"A mostrar eventos até <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para pesquisar eventos posteriores."</string>
+    <string name="search_title" msgid="8898015280250716254">"Pesquisar nos meus calendários"</string>
     <string name="num_events" msgid="3351672964607162257">"N.º de eventos"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Editar evento"</string>
     <string name="delete_label" msgid="874742811918030739">"Eliminar"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Eliminar evento"</string>
     <string name="save_label" msgid="2133599104834432589">"Concluído"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Reverter"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Cancelar"</string>
     <string name="import_label" msgid="6993796574741477222">"Importar"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Suspender todos"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Cancelar tudo"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Este evento será eliminado."</string>
     <string name="delete_title" msgid="5143743507524995383">"Eliminar"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Alterar resposta"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Definições"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Configuração de visualização do calendário"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Definições de lembrete"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Definições de lembrete"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Ocultar eventos recusados"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Definir alertas e notificações"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alertas e notificações"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"A semana começa"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"A semana começa na"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Limpar histórico de pesquisas"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Remover todas as pesquisas efectuadas"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Histórico de pesquisas limpo"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notificações"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrar"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrar também para lembretes"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Seleccionar toque"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notificação de pop-up"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Hora predefinida do lembrete"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Hora predefinida do lembrete"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,13 @@
     <item msgid="4552962311897985633">"Nunca"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrar"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Calendário"</string>
+    <!-- no translation found for gadget_more_events:one (1813062802420615657) -->
+    <!-- no translation found for gadget_more_events:other (7993512762778431065) -->
+    <!-- no translation found for gadget_no_events (5209110911088302855) -->
+    <skip />
+    <string name="tomorrow" msgid="614434811813859237">"Amanhã"</string>
+    <string name="in_progress" msgid="3637799356103489011">"em curso"</string>
+    <string name="today" msgid="8577375020252453006">"Hoje"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"A pesquisar <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml
index 4180148..4d01b00 100644
--- a/res/values-pt/arrays.xml
+++ b/res/values-pt/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dias"</item>
     <item msgid="3390146235648970198">"1 semana"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Nenhum"</item>
+    <item msgid="3377199512833209480">"1 minuto"</item>
+    <item msgid="5023205715881387516">"5 minutos"</item>
+    <item msgid="7720870540593319107">"10 minutos"</item>
+    <item msgid="1653841700859180907">"15 minutos"</item>
+    <item msgid="3741629746313107482">"20 minutos"</item>
+    <item msgid="5357458446224446398">"25 minutos"</item>
+    <item msgid="403143749684280703">"30 minutos"</item>
+    <item msgid="5611574008997362948">"45 minutos"</item>
+    <item msgid="30462793717552">"1 hora"</item>
+    <item msgid="119808229352802480">"2 horas"</item>
+    <item msgid="7456742278252094745">"3 horas"</item>
+    <item msgid="5263928189935026559">"12 horas"</item>
+    <item msgid="6451763179435252852">"24 horas"</item>
+    <item msgid="966666856467721784">"2 dias"</item>
+    <item msgid="5840973497558794742">"1 semana"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Localidade padrão"</item>
+    <item msgid="134027225275475280">"Sábado"</item>
+    <item msgid="95029346069903091">"Domingo"</item>
+    <item msgid="5840983116375063739">"Segunda-feira"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Ilhas Marshall"</item>
     <item msgid="7176569939440505575">"Midway"</item>
@@ -41,7 +65,7 @@
     <item msgid="1206600898319778734">"Horár. Pacífico"</item>
     <item msgid="4251553449779733536">"Tijuana"</item>
     <item msgid="4796372144693551811">"Arizona"</item>
-    <item msgid="4589419122570218444">"Chihuahua"</item>
+    <item msgid="4589419122570218444">"Chihuahua\n"</item>
     <item msgid="1653398220457872326">"Horár. Montanhas"</item>
     <item msgid="229005026661478023">"América Central"</item>
     <item msgid="1545268116831476742">"Horário Central"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrado"</item>
     <item msgid="3284290759549286849">"Bruxelas"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"Horário da África Ocidental"</item>
-    <item msgid="7527935722014187591">"Amã, Jordânia"</item>
-    <item msgid="672509109295227133">"Atenas, Istambul"</item>
-    <item msgid="1835470340349221646">"Beirute, Líbano"</item>
-    <item msgid="9006223109403693650">"Cairo"</item>
-    <item msgid="2877875673636361825">"Helsinque"</item>
-    <item msgid="4528124308590807049">"Jerusalém"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdá"</item>
-    <item msgid="8077211460729251609">"Moscou"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairóbi"</item>
-    <item msgid="1531618843539764728">"Teerã"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Ierevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Cabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Uralsk"</item>
-    <item msgid="4757644551253886090">"Yekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Rangum"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangcoc"</item>
-    <item msgid="4910549870048384487">"Pequim"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipé"</item>
-    <item msgid="3043691515163887690">"Seul"</item>
-    <item msgid="6369124969606372122">"Tóquio, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Camberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Nenhum"</item>
-    <item msgid="3377199512833209480">"1 minuto"</item>
-    <item msgid="5023205715881387516">"5 minutos"</item>
-    <item msgid="7720870540593319107">"10 minutos"</item>
-    <item msgid="1653841700859180907">"15 minutos"</item>
-    <item msgid="3741629746313107482">"20 minutos"</item>
-    <item msgid="5357458446224446398">"25 minutos"</item>
-    <item msgid="403143749684280703">"30 minutos"</item>
-    <item msgid="5611574008997362948">"45 minutos"</item>
-    <item msgid="30462793717552">"1 hora"</item>
-    <item msgid="119808229352802480">"2 horas"</item>
-    <item msgid="7456742278252094745">"3 horas"</item>
-    <item msgid="5263928189935026559">"12 horas"</item>
-    <item msgid="6451763179435252852">"24 horas"</item>
-    <item msgid="966666856467721784">"2 dias"</item>
-    <item msgid="5840973497558794742">"1 semana"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Alerta"</item>
-    <item msgid="8723674378160518790">"Notificação da barra de status"</item>
-    <item msgid="8433736231022186661">"Desativado"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"Hor. Áfr. Ocid."</item>
+    <item msgid="1358916275855835758">"Amã, Jordânia"</item>
+    <item msgid="5900185968201011015">"Atenas, Istambul"</item>
+    <item msgid="4975304496845689895">"Beirute, Líbano"</item>
+    <item msgid="4099790960681254059">"Cairo"</item>
+    <item msgid="4174072690298729061">"Helsinque"</item>
+    <item msgid="5233437715028921599">"Jerusalém"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdá"</item>
+    <item msgid="3703352987270492256">"Moscou"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairóbi"</item>
+    <item msgid="4216389209911081345">"Teerã"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Ierevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Cabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Uralsk"</item>
+    <item msgid="8779467771115356898">"Yekaterinburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Rangum"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangcoc"</item>
+    <item msgid="7651147941145227340">"Pequim"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipé"</item>
+    <item msgid="4972387553080120086">"Seul"</item>
+    <item msgid="5749349714875345358">"Tóquio, Osaka"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sidney, Camberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Ocupado(a)"</item>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e244f32..b75625b 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Agenda"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Agenda completa"</string>
     <string name="what_label" msgid="1933578391985810049">"O que"</string>
     <string name="where_label" msgid="3472482206065595118">"Onde"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Fuso horário"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Fuso horário"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Convidados"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Repetições"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dia"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dias"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Atualizar"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Mostrar compromissos"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Mostrar dia"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Compromissos"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Hoje"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Agendas"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Configurações"</string>
+    <string name="search" msgid="550656004486017426">"Pesquisar"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(mais <xliff:g id="MORE_COUNT">%d</xliff:g> a mais …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Agendas"</string>
     <string name="synced_visible" msgid="1406744353749335651">"sincronizadas, visíveis"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"sincronizada, não visível"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"não sincronizada, não visível"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Detalhes do evento"</string>
     <string name="hint_what" msgid="709155115005044531">"Nome do evento"</string>
     <string name="hint_where" msgid="3116239630502213241">"Local do evento"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Agenda"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Mostrar opções extras"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ocultar opções extras"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Mostrar todas &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Mostrar todos"</string>
     <string name="description_label" msgid="7193475046717505855">"Descrição"</string>
     <string name="presence_label" msgid="6169724148441175862">"Mostrar-me como"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Privacidade"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Lembretes"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Nenhuma agenda disponível"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Você não tem nenhuma agenda."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Data de início"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Hora de início"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Data de término"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Hora de término"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Lembretes"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Remover lembrete"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Agenda:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organizador:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Localizar fuso horário:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Você participará?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Hoje, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Carregando..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Mostrando eventos desde <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para procurar mais."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Mostrando eventos até <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para procurar mais."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Mostrando eventos até <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para buscar eventos anteriores."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Mostrando eventos até <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Toque para buscar eventos posteriores."</string>
+    <string name="search_title" msgid="8898015280250716254">"Pesquisar minhas agendas"</string>
     <string name="num_events" msgid="3351672964607162257">"Núm. eventos"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Editar evento"</string>
     <string name="delete_label" msgid="874742811918030739">"Excluir"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Excluir evento"</string>
     <string name="save_label" msgid="2133599104834432589">"Concluído"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Reverter"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Cancelar"</string>
     <string name="import_label" msgid="6993796574741477222">"Importar"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Colocar todos em modo de espera"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Encerrar tudo"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Este evento será excluído."</string>
     <string name="delete_title" msgid="5143743507524995383">"Excluir"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Alterar resposta"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Configurações"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Configuração de visualização da agenda"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Configurações dos lembretes"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Configurações dos lembretes"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Ocultar recusados"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Alertas e notificações"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Alertas e notificações"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"A semana começa em"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"A semana começa em"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Limpar histórico de pesquisa"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Remove todas as pesquisas que você já fez"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Histórico de pesquisa removido"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Notificações"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrar"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrar também para lembretes"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Selecionar toque"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Notificação pop-up"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Tempo do lembrete"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Momento padrão do lembrete"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Nunca"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrar"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Agenda"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"Mais 1 evento"</item>
+    <item quantity="other" msgid="7993512762778431065">"Mais <xliff:g id="NUMBER">%d</xliff:g> eventos"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Nenhum evento futuro na agenda"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Amanhã"</string>
+    <string name="in_progress" msgid="3637799356103489011">"em andamento"</string>
+    <string name="today" msgid="8577375020252453006">"Hoje"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Pesquisando <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
index b8e17fe..559118f 100644
--- a/res/values-ru/arrays.xml
+++ b/res/values-ru/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"за 2 дня"</item>
     <item msgid="3390146235648970198">"за 1 неделю"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Нет"</item>
+    <item msgid="3377199512833209480">"за 1 минуту"</item>
+    <item msgid="5023205715881387516">"за 5 минут"</item>
+    <item msgid="7720870540593319107">"за 10 минут"</item>
+    <item msgid="1653841700859180907">"за 15 минут"</item>
+    <item msgid="3741629746313107482">"за 20 минут"</item>
+    <item msgid="5357458446224446398">"за 25 минут"</item>
+    <item msgid="403143749684280703">"за 30 минут"</item>
+    <item msgid="5611574008997362948">"за 45 минут"</item>
+    <item msgid="30462793717552">"за 1 час"</item>
+    <item msgid="119808229352802480">"за 2 часа"</item>
+    <item msgid="7456742278252094745">"за 3 часа"</item>
+    <item msgid="5263928189935026559">"за 12 часов"</item>
+    <item msgid="6451763179435252852">"за 24 часа"</item>
+    <item msgid="966666856467721784">"за 2 дня"</item>
+    <item msgid="5840973497558794742">"за 1 неделю"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Региональные настройки по умолчанию"</item>
+    <item msgid="134027225275475280">"суббота"</item>
+    <item msgid="95029346069903091">"воскресенье"</item>
+    <item msgid="5840983116375063739">"понедельник"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Маршалловы о-ва"</item>
     <item msgid="7176569939440505575">"Остров Мидуэй"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Белград"</item>
     <item msgid="3284290759549286849">"Брюссель"</item>
     <item msgid="3112943938015033049">"Сараево"</item>
-    <item msgid="2184425710639965762">"Париж"</item>
-    <item msgid="5997630882187580225">"Виндхук"</item>
-    <item msgid="3828800493609994208">"Зап.-афр. время"</item>
-    <item msgid="7527935722014187591">"Амман, Иордания"</item>
-    <item msgid="672509109295227133">"Афины, Стамбул"</item>
-    <item msgid="1835470340349221646">"Бейрут, Ливан"</item>
-    <item msgid="9006223109403693650">"Каир"</item>
-    <item msgid="2877875673636361825">"Хельсинки"</item>
-    <item msgid="4528124308590807049">"Иерусалим"</item>
-    <item msgid="7823856457885290195">"Минск"</item>
-    <item msgid="8650995074282357197">"Хараре"</item>
-    <item msgid="209523815326871285">"Багдад"</item>
-    <item msgid="8077211460729251609">"Москва"</item>
-    <item msgid="6103383184232804522">"Кувейт"</item>
-    <item msgid="8581935265080466099">"Найроби"</item>
-    <item msgid="1531618843539764728">"Тегеран"</item>
-    <item msgid="141548505188106312">"Баку"</item>
-    <item msgid="5974315340250149692">"Тбилиси"</item>
-    <item msgid="3344801680659888795">"Ереван"</item>
-    <item msgid="2537944335416132075">"Дубай"</item>
-    <item msgid="5630432498109110949">"Кабул"</item>
-    <item msgid="2274762525650993846">"Исламабад, Карачи"</item>
-    <item msgid="272153171103339242">"Уральск"</item>
-    <item msgid="4757644551253886090">"Екатеринбург"</item>
-    <item msgid="3036512479614493957">"Калькутта"</item>
-    <item msgid="4190271241277312277">"Шри-Ланка"</item>
-    <item msgid="4489139826358657866">"Катманду"</item>
-    <item msgid="1195461607876034232">"Астана"</item>
-    <item msgid="8777509327730950228">"Янгон"</item>
-    <item msgid="3838899720161825998">"Красноярск"</item>
-    <item msgid="6495532062164690437">"Бангкок"</item>
-    <item msgid="4910549870048384487">"Пекин"</item>
-    <item msgid="7174954011332328153">"Гонконг"</item>
-    <item msgid="415034182139280334">"Иркутск"</item>
-    <item msgid="3407263784503834649">"Куала-Лумпур"</item>
-    <item msgid="6547945295249807755">"Перт"</item>
-    <item msgid="4571776847411430193">"Тайбэй"</item>
-    <item msgid="3043691515163887690">"Сеул"</item>
-    <item msgid="6369124969606372122">"Токио, Осака"</item>
-    <item msgid="8317842783685169193">"Якутск"</item>
-    <item msgid="6993206457323990990">"Аделаида"</item>
-    <item msgid="2431970257749452312">"Дарвин"</item>
-    <item msgid="2709489188606506801">"Брисбен"</item>
-    <item msgid="6832511138206913044">"Хобарт"</item>
-    <item msgid="6933429790996727761">"Сидней, Канберра"</item>
-    <item msgid="4546625974877459074">"Владивосток"</item>
-    <item msgid="7421491250467489614">"Гуам"</item>
-    <item msgid="3450367738093440984">"Магадан"</item>
-    <item msgid="5046540868552082971">"Окленд"</item>
-    <item msgid="481903191189494072">"Фиджи"</item>
-    <item msgid="1926932394586102998">"Тонга"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Нет"</item>
-    <item msgid="3377199512833209480">"за 1 минуту"</item>
-    <item msgid="5023205715881387516">"за 5 минут"</item>
-    <item msgid="7720870540593319107">"за 10 минут"</item>
-    <item msgid="1653841700859180907">"за 15 минут"</item>
-    <item msgid="3741629746313107482">"за 20 минут"</item>
-    <item msgid="5357458446224446398">"за 25 минут"</item>
-    <item msgid="403143749684280703">"за 30 минут"</item>
-    <item msgid="5611574008997362948">"за 45 минут"</item>
-    <item msgid="30462793717552">"за 1 час"</item>
-    <item msgid="119808229352802480">"за 2 часа"</item>
-    <item msgid="7456742278252094745">"за 3 часа"</item>
-    <item msgid="5263928189935026559">"за 12 часов"</item>
-    <item msgid="6451763179435252852">"за 24 часа"</item>
-    <item msgid="966666856467721784">"за 2 дня"</item>
-    <item msgid="5840973497558794742">"за 1 неделю"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Оповещение"</item>
-    <item msgid="8723674378160518790">"Уведомление в строке состояния"</item>
-    <item msgid="8433736231022186661">"Выкл."</item>
+    <item msgid="1746966478800108111">"Виндхук"</item>
+    <item msgid="7008598576103662879">"Зап.-афр. время"</item>
+    <item msgid="1358916275855835758">"Амман, Иордания"</item>
+    <item msgid="5900185968201011015">"Афины, Стамбул"</item>
+    <item msgid="4975304496845689895">"Бейрут, Ливан"</item>
+    <item msgid="4099790960681254059">"Каир"</item>
+    <item msgid="4174072690298729061">"Хельсинки"</item>
+    <item msgid="5233437715028921599">"Иерусалим"</item>
+    <item msgid="612137838544847325">"Минск"</item>
+    <item msgid="385832033162858704">"Хараре"</item>
+    <item msgid="1439592855284837653">"Багдад"</item>
+    <item msgid="3703352987270492256">"Москва"</item>
+    <item msgid="1657996636394546903">"Кувейт"</item>
+    <item msgid="3719675800096223903">"Найроби"</item>
+    <item msgid="4216389209911081345">"Тегеран"</item>
+    <item msgid="4949871918709805735">"Баку"</item>
+    <item msgid="2863183321532922311">"Тбилиси"</item>
+    <item msgid="1943529325618264999">"Ереван"</item>
+    <item msgid="5094507376721687886">"Дубаи"</item>
+    <item msgid="2849654571454095226">"Кабул"</item>
+    <item msgid="3641915655045056824">"Исламабад, Карачи"</item>
+    <item msgid="5795351343828254400">"Уральск"</item>
+    <item msgid="8779467771115356898">"Екатеринбург"</item>
+    <item msgid="297412491658718331">"Калькутта"</item>
+    <item msgid="2290076787008042202">"Шри-Ланка"</item>
+    <item msgid="4119703758417825749">"Катманду"</item>
+    <item msgid="9161760299912900568">"Астана"</item>
+    <item msgid="5753596907402377327">"Янгон"</item>
+    <item msgid="6275146302340764856">"Красноярск"</item>
+    <item msgid="2385588555356900033">"Бангкок"</item>
+    <item msgid="7651147941145227340">"Пекин"</item>
+    <item msgid="7831696303531874014">"Гонконг"</item>
+    <item msgid="7943498021949728203">"Иркутск"</item>
+    <item msgid="707009198021550118">"Куала-Лумпур"</item>
+    <item msgid="4268864549735629220">"Перт"</item>
+    <item msgid="4584239166170514795">"Тайбэй"</item>
+    <item msgid="4972387553080120086">"Сеул"</item>
+    <item msgid="5749349714875345358">"Токио, Осака"</item>
+    <item msgid="4435870687083770252">"Якутск"</item>
+    <item msgid="4857952326324510625">"Аделаида"</item>
+    <item msgid="7802545626741434274">"Дарвин"</item>
+    <item msgid="7184691104734480971">"Брисбен"</item>
+    <item msgid="7795651165061255022">"Хобарт"</item>
+    <item msgid="1858052331367859514">"Сидней, Канберра"</item>
+    <item msgid="4396794682654884440">"Владивосток"</item>
+    <item msgid="4152655496917506653">"Гуам"</item>
+    <item msgid="3608508135351982746">"Магадан"</item>
+    <item msgid="722180563708274099">"Окленд"</item>
+    <item msgid="1736037231626139044">"Фиджи"</item>
+    <item msgid="8187746821081424875">"Тонга"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Занят"</item>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 22ef6cd..f8885bf 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Календарь"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Календарь \"все в одном\""</string>
     <string name="what_label" msgid="1933578391985810049">"Что"</string>
     <string name="where_label" msgid="3472482206065595118">"Где"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Часовой пояс"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Часовой пояс"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Гости"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Повторять"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 день"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> дн."</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Обновить"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Показать повестку дня"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Показать день"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Повестка дня"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Сегодня"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Календари"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Настройки"</string>
+    <string name="search" msgid="550656004486017426">"Поиск"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(и ещё <xliff:g id="MORE_COUNT">%d</xliff:g>…)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Календари"</string>
     <string name="synced_visible" msgid="1406744353749335651">"синхронизировано, видимое"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"синхронизировано, невидимое"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"не синхронизировано, невидимое"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Детали мероприятия"</string>
     <string name="hint_what" msgid="709155115005044531">"Название мероприятия"</string>
     <string name="hint_where" msgid="3116239630502213241">"Место мероприятия"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Календарь"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Показать дополнительные параметры"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Скрыть дополнительные параметры"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Показать все &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Показать все"</string>
     <string name="description_label" msgid="7193475046717505855">"Описание"</string>
     <string name="presence_label" msgid="6169724148441175862">"Мой статус:"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Конфиденциальность"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Напоминания"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Нет календарей"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"У вас нет календарей."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Дата начала"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Время начала"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Дата конца"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Время конца"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Напоминания"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Удалить напоминание"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Календарь:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Организатор:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Часовой пояс:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Принять приглашение?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Сегодня, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Загрузка…"</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Мероприятия с <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Нажмите для поиска других мероприятий."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Мероприятия до <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Нажмите для поиска других мероприятий."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Показаны мероприятия с <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Нажмите, чтобы найти более ранние мероприятия."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Показаны мероприятия до <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Нажмите, чтобы найти более поздние мероприятия."</string>
+    <string name="search_title" msgid="8898015280250716254">"Поиск в моих календарях"</string>
     <string name="num_events" msgid="3351672964607162257">"Кол-во мероприятий"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Изменить мероприятие"</string>
     <string name="delete_label" msgid="874742811918030739">"Удалить"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Удалить мероприятие"</string>
     <string name="save_label" msgid="2133599104834432589">"Готово"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Отмена"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Отмена"</string>
     <string name="import_label" msgid="6993796574741477222">"Импорт"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Отложить все"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Отключить все"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Мероприятие будет удалено."</string>
     <string name="delete_title" msgid="5143743507524995383">"Удалить"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Изменить ответ"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Настройки"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Мероприятия"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Настройки напоминаний"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Настройки напоминаний"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Скрыть отклоненные"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Настроить уведомления"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Оповещения и уведомления"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Начало недели:"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Начало недели:"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Очистить историю поиска"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Удалить информацию о всех ваших поисковых запросах"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"История поиска очищена"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Оповещения"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Вибросигнал"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Вибросигнал при напоминаниях"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Звук уведомления"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Всплывающее уведомление"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Время напоминания"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Напомнить"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"Никогда"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Вибросигнал"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Календарь"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"Еще одно событие"</item>
+    <item quantity="other" msgid="7993512762778431065">"Еще событий: <xliff:g id="NUMBER">%d</xliff:g>"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"Нет предстоящих событий календаря"</string>
+    <string name="tomorrow" msgid="614434811813859237">"Завтра"</string>
+    <string name="in_progress" msgid="3637799356103489011">"в процессе"</string>
+    <string name="today" msgid="8577375020252453006">"Сегодня"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Поиск в <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml
index 62b293a..0255213 100644
--- a/res/values-sv/arrays.xml
+++ b/res/values-sv/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 dagar"</item>
     <item msgid="3390146235648970198">"1 vecka"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Inga"</item>
+    <item msgid="3377199512833209480">"1 minut"</item>
+    <item msgid="5023205715881387516">"5 minuter"</item>
+    <item msgid="7720870540593319107">"10 minuter"</item>
+    <item msgid="1653841700859180907">"15 minuter"</item>
+    <item msgid="3741629746313107482">"20 minuter"</item>
+    <item msgid="5357458446224446398">"25 minuter"</item>
+    <item msgid="403143749684280703">"30 minuter"</item>
+    <item msgid="5611574008997362948">"45 minuter"</item>
+    <item msgid="30462793717552">"1 timme"</item>
+    <item msgid="119808229352802480">"2 timmar"</item>
+    <item msgid="7456742278252094745">"3 timmar"</item>
+    <item msgid="5263928189935026559">"12 timmar"</item>
+    <item msgid="6451763179435252852">"24 timmar"</item>
+    <item msgid="966666856467721784">"2 dagar"</item>
+    <item msgid="5840973497558794742">"1 vecka"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Standardinställning för språkkod"</item>
+    <item msgid="134027225275475280">"lördag"</item>
+    <item msgid="95029346069903091">"söndag"</item>
+    <item msgid="5840983116375063739">"måndag"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshallöarna"</item>
     <item msgid="7176569939440505575">"Midwayön"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrad"</item>
     <item msgid="3284290759549286849">"Bryssel"</item>
     <item msgid="3112943938015033049">"Sarajevo"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"West Africa Time"</item>
-    <item msgid="7527935722014187591">"Amman, Jordanien"</item>
-    <item msgid="672509109295227133">"Aten, Istanbul"</item>
-    <item msgid="1835470340349221646">"Beirut, Libanon"</item>
-    <item msgid="9006223109403693650">"Kairo"</item>
-    <item msgid="2877875673636361825">"Helsingfors"</item>
-    <item msgid="4528124308590807049">"Jerusalem"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bagdad"</item>
-    <item msgid="8077211460729251609">"Moskva"</item>
-    <item msgid="6103383184232804522">"Kuwait"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Teheran"</item>
-    <item msgid="141548505188106312">"Baku"</item>
-    <item msgid="5974315340250149692">"Tbilisi"</item>
-    <item msgid="3344801680659888795">"Jerevan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabul"</item>
-    <item msgid="2274762525650993846">"Islamabad, Karachi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Jekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Kathmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnojarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Peking"</item>
-    <item msgid="7174954011332328153">"Hongkong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Söul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Jakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Inga"</item>
-    <item msgid="3377199512833209480">"1 minut"</item>
-    <item msgid="5023205715881387516">"5 minuter"</item>
-    <item msgid="7720870540593319107">"10 minuter"</item>
-    <item msgid="1653841700859180907">"15 minuter"</item>
-    <item msgid="3741629746313107482">"20 minuter"</item>
-    <item msgid="5357458446224446398">"25 minuter"</item>
-    <item msgid="403143749684280703">"30 minuter"</item>
-    <item msgid="5611574008997362948">"45 minuter"</item>
-    <item msgid="30462793717552">"1 timme"</item>
-    <item msgid="119808229352802480">"2 timmar"</item>
-    <item msgid="7456742278252094745">"3 timmar"</item>
-    <item msgid="5263928189935026559">"12 timmar"</item>
-    <item msgid="6451763179435252852">"24 timmar"</item>
-    <item msgid="966666856467721784">"2 dagar"</item>
-    <item msgid="5840973497558794742">"1 vecka"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Varning"</item>
-    <item msgid="8723674378160518790">"Avisering i statusfältet"</item>
-    <item msgid="8433736231022186661">"Av"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"West Africa Time"</item>
+    <item msgid="1358916275855835758">"Amman, Jordanien"</item>
+    <item msgid="5900185968201011015">"Aten, Istanbul"</item>
+    <item msgid="4975304496845689895">"Beirut, Libanon"</item>
+    <item msgid="4099790960681254059">"Kairo"</item>
+    <item msgid="4174072690298729061">"Helsingfors"</item>
+    <item msgid="5233437715028921599">"Jerusalem"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bagdad"</item>
+    <item msgid="3703352987270492256">"Moskva"</item>
+    <item msgid="1657996636394546903">"Kuwait"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Teheran"</item>
+    <item msgid="4949871918709805735">"Baku"</item>
+    <item msgid="2863183321532922311">"Tbilisi"</item>
+    <item msgid="1943529325618264999">"Jerevan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabul"</item>
+    <item msgid="3641915655045056824">"Islamabad, Karachi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Jekaterinburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Kathmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnojarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Peking"</item>
+    <item msgid="7831696303531874014">"Hongkong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Söul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Jakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Upptagen"</item>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7f8984f..a98fb8c 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Kalender"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"Kalender AllInOne"</string>
     <string name="what_label" msgid="1933578391985810049">"Vad"</string>
     <string name="where_label" msgid="3472482206065595118">"Var"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Tidszon"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Tidszon"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Gäster"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Upprepning"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 dag"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> dagar"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Uppdatera"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Visa agenda"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Visa dag"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Agenda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Idag"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Kalendrar"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Inställningar"</string>
+    <string name="search" msgid="550656004486017426">"Sök"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(och <xliff:g id="MORE_COUNT">%d</xliff:g> till …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Kalendrar"</string>
     <string name="synced_visible" msgid="1406744353749335651">"synkat, visas"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"synkat, visas inte"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"inte synkat, visas inte"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Händelseinfo"</string>
     <string name="hint_what" msgid="709155115005044531">"Händelsens namn"</string>
     <string name="hint_where" msgid="3116239630502213241">"Plats för händelsen"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Kalender"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Visa fler alternativ"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Dölj fler alternativ"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Visa alla &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Visa alla"</string>
     <string name="description_label" msgid="7193475046717505855">"Beskrivning"</string>
     <string name="presence_label" msgid="6169724148441175862">"Visa mig som"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Sekretess"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Påminnelser"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Inga kalendrar"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Du har inga kalendrar."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Från datum"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Från tid"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Till datum"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Till tid"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Påminnelser"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Ta bort påminnelse"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Kalender:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Organisatör:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Lokal tidszon:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Delta?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Idag, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Hämtar…"</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Visar händelser sedan <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>. Peka om du vill visa fler."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Visar händelser fram till <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>. Peka om du vill visa fler."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"Händelser sedan <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> visas. Tryck om du vill söka efter tidigare händelser."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"Händelser fram till <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g> visas. Tryck om du vill söka efter senare händelser."</string>
+    <string name="search_title" msgid="8898015280250716254">"Sök i mina kalendrar"</string>
     <string name="num_events" msgid="3351672964607162257">"Antal händelser"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Redigera händelse"</string>
     <string name="delete_label" msgid="874742811918030739">"Ta bort"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Ta bort händelse"</string>
     <string name="save_label" msgid="2133599104834432589">"Färdig"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Återgå"</string>
+    <string name="discard_label" msgid="2394623883289782541">"Avbryt"</string>
     <string name="import_label" msgid="6993796574741477222">"Importera"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Sätt alla på snooze"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Ta bort alla permanent"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Händelsen kommer att tas bort."</string>
     <string name="delete_title" msgid="5143743507524995383">"Ta bort"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Ändra svar"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Inställningar"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Inställning för kalendervy"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Inställningar för påminnelse"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Inställningar för påminnelse"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Dölj avböjda händelser"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Ställ in varningar och aviseringar"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Varningar och aviseringar"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Veckan börjar med"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Veckan börjar med"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Rensa sökhistorik"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Ta bort alla sökningar du har gjort"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Sökhistoriken rensad"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Aviseringar"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Vibrera"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Vibrera även vid påminnelser"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Välj ringsignal"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Popup-meddelande"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Standardtid för påminnelse"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Standardtid för påminnelse"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,13 @@
     <item msgid="4552962311897985633">"Aldrig"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Vibrera"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Kalender"</string>
+    <!-- no translation found for gadget_more_events:one (1813062802420615657) -->
+    <!-- no translation found for gadget_more_events:other (7993512762778431065) -->
+    <!-- no translation found for gadget_no_events (5209110911088302855) -->
+    <skip />
+    <string name="tomorrow" msgid="614434811813859237">"Imorgon"</string>
+    <string name="in_progress" msgid="3637799356103489011">"bearbetas"</string>
+    <string name="today" msgid="8577375020252453006">"Idag"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"Söker <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml
index 51f5ce6..0bfa255 100644
--- a/res/values-tr/arrays.xml
+++ b/res/values-tr/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 gün"</item>
     <item msgid="3390146235648970198">"1 hafta"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"Yok"</item>
+    <item msgid="3377199512833209480">"1 dakika"</item>
+    <item msgid="5023205715881387516">"5 dakika"</item>
+    <item msgid="7720870540593319107">"10 dakika"</item>
+    <item msgid="1653841700859180907">"15 dakika"</item>
+    <item msgid="3741629746313107482">"20 dakika"</item>
+    <item msgid="5357458446224446398">"25 dakika"</item>
+    <item msgid="403143749684280703">"30 dakika"</item>
+    <item msgid="5611574008997362948">"45 dakika"</item>
+    <item msgid="30462793717552">"1 saat"</item>
+    <item msgid="119808229352802480">"2 saat"</item>
+    <item msgid="7456742278252094745">"3 saat"</item>
+    <item msgid="5263928189935026559">"12 saat"</item>
+    <item msgid="6451763179435252852">"24 saat"</item>
+    <item msgid="966666856467721784">"2 gün"</item>
+    <item msgid="5840973497558794742">"1 hafta"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"Varsayılan yerel ayar"</item>
+    <item msgid="134027225275475280">"Cumartesi"</item>
+    <item msgid="95029346069903091">"Pazar"</item>
+    <item msgid="5840983116375063739">"Pazartesi"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"Marshall Adaları"</item>
     <item msgid="7176569939440505575">"Midway Adası"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"Belgrad"</item>
     <item msgid="3284290759549286849">"Brüksel"</item>
     <item msgid="3112943938015033049">"Saraybosna"</item>
-    <item msgid="2184425710639965762">"Paris"</item>
-    <item msgid="5997630882187580225">"Windhoek"</item>
-    <item msgid="3828800493609994208">"B. Afrika Saati"</item>
-    <item msgid="7527935722014187591">"Amman, Ürdün"</item>
-    <item msgid="672509109295227133">"Atina, İstanbul"</item>
-    <item msgid="1835470340349221646">"Beyrut, Lübnan"</item>
-    <item msgid="9006223109403693650">"Kahire"</item>
-    <item msgid="2877875673636361825">"Helsinki"</item>
-    <item msgid="4528124308590807049">"Kudüs"</item>
-    <item msgid="7823856457885290195">"Minsk"</item>
-    <item msgid="8650995074282357197">"Harare"</item>
-    <item msgid="209523815326871285">"Bağdat"</item>
-    <item msgid="8077211460729251609">"Moskova"</item>
-    <item msgid="6103383184232804522">"Kuveyt"</item>
-    <item msgid="8581935265080466099">"Nairobi"</item>
-    <item msgid="1531618843539764728">"Tahran"</item>
-    <item msgid="141548505188106312">"Bakü"</item>
-    <item msgid="5974315340250149692">"Tiflis"</item>
-    <item msgid="3344801680659888795">"Erivan"</item>
-    <item msgid="2537944335416132075">"Dubai"</item>
-    <item msgid="5630432498109110949">"Kabil"</item>
-    <item msgid="2274762525650993846">"İslamabat, Karaçi"</item>
-    <item msgid="272153171103339242">"Ural\'sk"</item>
-    <item msgid="4757644551253886090">"Yekaterinburg"</item>
-    <item msgid="3036512479614493957">"Kolkata"</item>
-    <item msgid="4190271241277312277">"Sri Lanka"</item>
-    <item msgid="4489139826358657866">"Katmandu"</item>
-    <item msgid="1195461607876034232">"Astana"</item>
-    <item msgid="8777509327730950228">"Yangon"</item>
-    <item msgid="3838899720161825998">"Krasnoyarsk"</item>
-    <item msgid="6495532062164690437">"Bangkok"</item>
-    <item msgid="4910549870048384487">"Pekin"</item>
-    <item msgid="7174954011332328153">"Hong Kong"</item>
-    <item msgid="415034182139280334">"Irkutsk"</item>
-    <item msgid="3407263784503834649">"Kuala Lumpur"</item>
-    <item msgid="6547945295249807755">"Perth"</item>
-    <item msgid="4571776847411430193">"Taipei"</item>
-    <item msgid="3043691515163887690">"Seul"</item>
-    <item msgid="6369124969606372122">"Tokyo, Osaka"</item>
-    <item msgid="8317842783685169193">"Yakutsk"</item>
-    <item msgid="6993206457323990990">"Adelaide"</item>
-    <item msgid="2431970257749452312">"Darwin"</item>
-    <item msgid="2709489188606506801">"Brisbane"</item>
-    <item msgid="6832511138206913044">"Hobart"</item>
-    <item msgid="6933429790996727761">"Sydney, Canberra"</item>
-    <item msgid="4546625974877459074">"Vladivostok"</item>
-    <item msgid="7421491250467489614">"Guam"</item>
-    <item msgid="3450367738093440984">"Magadan"</item>
-    <item msgid="5046540868552082971">"Auckland"</item>
-    <item msgid="481903191189494072">"Fiji"</item>
-    <item msgid="1926932394586102998">"Tonga"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"Yok"</item>
-    <item msgid="3377199512833209480">"1 dakika"</item>
-    <item msgid="5023205715881387516">"5 dakika"</item>
-    <item msgid="7720870540593319107">"10 dakika"</item>
-    <item msgid="1653841700859180907">"15 dakika"</item>
-    <item msgid="3741629746313107482">"20 dakika"</item>
-    <item msgid="5357458446224446398">"25 dakika"</item>
-    <item msgid="403143749684280703">"30 dakika"</item>
-    <item msgid="5611574008997362948">"45 dakika"</item>
-    <item msgid="30462793717552">"1 saat"</item>
-    <item msgid="119808229352802480">"2 saat"</item>
-    <item msgid="7456742278252094745">"3 saat"</item>
-    <item msgid="5263928189935026559">"12 saat"</item>
-    <item msgid="6451763179435252852">"24 saat"</item>
-    <item msgid="966666856467721784">"2 gün"</item>
-    <item msgid="5840973497558794742">"1 hafta"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"Uyarı"</item>
-    <item msgid="8723674378160518790">"Durum çubuğu bildirimi"</item>
-    <item msgid="8433736231022186661">"Kapalı"</item>
+    <item msgid="1746966478800108111">"Windhoek"</item>
+    <item msgid="7008598576103662879">"B. Afrika Saati"</item>
+    <item msgid="1358916275855835758">"Amman, Ürdün"</item>
+    <item msgid="5900185968201011015">"Atina, İstanbul"</item>
+    <item msgid="4975304496845689895">"Beyrut, Lübnan"</item>
+    <item msgid="4099790960681254059">"Kahire"</item>
+    <item msgid="4174072690298729061">"Helsinki"</item>
+    <item msgid="5233437715028921599">"Kudüs"</item>
+    <item msgid="612137838544847325">"Minsk"</item>
+    <item msgid="385832033162858704">"Harare"</item>
+    <item msgid="1439592855284837653">"Bağdat"</item>
+    <item msgid="3703352987270492256">"Moskova"</item>
+    <item msgid="1657996636394546903">"Kuveyt"</item>
+    <item msgid="3719675800096223903">"Nairobi"</item>
+    <item msgid="4216389209911081345">"Tahran"</item>
+    <item msgid="4949871918709805735">"Bakü"</item>
+    <item msgid="2863183321532922311">"Tiflis"</item>
+    <item msgid="1943529325618264999">"Erivan"</item>
+    <item msgid="5094507376721687886">"Dubai"</item>
+    <item msgid="2849654571454095226">"Kabil"</item>
+    <item msgid="3641915655045056824">"İslamabat, Karaçi"</item>
+    <item msgid="5795351343828254400">"Ural\'sk"</item>
+    <item msgid="8779467771115356898">"Yekaterinburg"</item>
+    <item msgid="297412491658718331">"Kolkata"</item>
+    <item msgid="2290076787008042202">"Sri Lanka"</item>
+    <item msgid="4119703758417825749">"Katmandu"</item>
+    <item msgid="9161760299912900568">"Astana"</item>
+    <item msgid="5753596907402377327">"Yangon"</item>
+    <item msgid="6275146302340764856">"Krasnoyarsk"</item>
+    <item msgid="2385588555356900033">"Bangkok"</item>
+    <item msgid="7651147941145227340">"Pekin"</item>
+    <item msgid="7831696303531874014">"Hong Kong"</item>
+    <item msgid="7943498021949728203">"Irkutsk"</item>
+    <item msgid="707009198021550118">"Kuala Lumpur"</item>
+    <item msgid="4268864549735629220">"Perth"</item>
+    <item msgid="4584239166170514795">"Taipei"</item>
+    <item msgid="4972387553080120086">"Seul"</item>
+    <item msgid="5749349714875345358">"Tokyo, Osaka"</item>
+    <item msgid="4435870687083770252">"Yakutsk"</item>
+    <item msgid="4857952326324510625">"Adelaide"</item>
+    <item msgid="7802545626741434274">"Darwin"</item>
+    <item msgid="7184691104734480971">"Brisbane"</item>
+    <item msgid="7795651165061255022">"Hobart"</item>
+    <item msgid="1858052331367859514">"Sydney, Canberra"</item>
+    <item msgid="4396794682654884440">"Vladivostok"</item>
+    <item msgid="4152655496917506653">"Guam"</item>
+    <item msgid="3608508135351982746">"Magadan"</item>
+    <item msgid="722180563708274099">"Auckland"</item>
+    <item msgid="1736037231626139044">"Fiji"</item>
+    <item msgid="8187746821081424875">"Tonga"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"Meşgul"</item>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index d034350..6595a57 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Takvim"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"AllInOne Takvim"</string>
     <string name="what_label" msgid="1933578391985810049">"Başlık:"</string>
     <string name="where_label" msgid="3472482206065595118">"Yer"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"Saat dilimi"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"Saat dilimi"</string>
     <string name="attendees_label" msgid="2971281641565729725">"Misafirler"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"Tekrar"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 gün"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> gün"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"Yenile"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"Ajandayı göster"</string>
     <string name="show_day_view" msgid="5782512782605466016">"Günü göster"</string>
     <string name="agenda_view" msgid="5566084759423843245">"Ajanda"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"Bugün"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"Takvimler"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"Ayarlar"</string>
+    <string name="search" msgid="550656004486017426">"Ara"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(artı <xliff:g id="MORE_COUNT">%d</xliff:g> tane daha …)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"Takvimler"</string>
     <string name="synced_visible" msgid="1406744353749335651">"senkronize edildi, görülebilir"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"senkronize edildi, görülebilir değil"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"senkronize edilmedi, görülebilir değil"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"Etkinlik ayrıntıları"</string>
     <string name="hint_what" msgid="709155115005044531">"Etkinlik adı"</string>
     <string name="hint_where" msgid="3116239630502213241">"Etkinlik konumu"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"Takvim"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"Ek seçenekleri göster"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"Ek seçenekleri gizle"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"Tümünü göster &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"Tümünü göster"</string>
     <string name="description_label" msgid="7193475046717505855">"Açıklama"</string>
     <string name="presence_label" msgid="6169724148441175862">"Durumumu şu şekilde göster:"</string>
     <string name="privacy_label" msgid="9215031965259597335">"Gizlilik"</string>
     <string name="reminders_label" msgid="8345054160145333166">"Hatırlatıcılar"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"Takvim yok"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"Hiç takviminiz yok."</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"Şu tarihten:"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"Şu saatten:"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"Şu tarihe kadar:"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"Şu saate kadar:"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"Hatırlatıcılar"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"Hatırlatıcıyı kaldır"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"Takvim:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"Düzenleyici:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"Yerel saat dilimi:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"Katılıyor musunuz?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"Bugün, <xliff:g id="DATE">%1$s</xliff:g>"</string>
     <string name="loading" msgid="3772533493214331230">"Yükleniyor…"</string>
-    <string name="show_older_events" msgid="4031960000704544741">"Etkinlikler <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> tarihinden itibaren gösteriliyor. Daha fazlasını görmek için hafifçe vurun."</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"Etkinlikler <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g> tarihine kadar gösteriliyor. Daha fazlasını görmek için hafifçe vurun."</string>
+    <string name="show_older_events" msgid="6819097859184398737">"<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g> tarihinden bugüne kadarki etkinlikler gösteriliyor. Daha önceki etkinlikleri aramak için dokunun."</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g> tarihine kadar olan etkinlikler gösteriliyor. Sonraki etkinlikleri aramak için dokunun."</string>
+    <string name="search_title" msgid="8898015280250716254">"Takvimlerimde arama"</string>
     <string name="num_events" msgid="3351672964607162257">"Etkinlik sayısı"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"Etkinliği düzenle"</string>
     <string name="delete_label" msgid="874742811918030739">"Sil"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"Etkinliği sil"</string>
     <string name="save_label" msgid="2133599104834432589">"Bitti"</string>
-    <string name="discard_label" msgid="4510607554910139220">"Geri Döndür"</string>
+    <string name="discard_label" msgid="2394623883289782541">"İptal"</string>
     <string name="import_label" msgid="6993796574741477222">"İçe aktar"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"Tümünü ertele"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"Tümünü kapat"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"Bu etkinlik silinecek."</string>
     <string name="delete_title" msgid="5143743507524995383">"Sil"</string>
     <string name="change_response_title" msgid="7704714776070974984">"Yanıtı değiştir"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"Ayarlar"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"Takvim görünümü ayarı"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"Hatırlatıcı ayarları"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"Hatırlatıcı ayarları"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"Reddedilmiş etkin. gizle"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"Uyarı ve bildirimleri ayarla"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"Uyarılar ve bildirimler"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"Haftanın ilk günü"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"Haftanın ilk günü"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"Arama geçmişini temizle"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"Gerçekleştirdiğiniz tüm aramaları kaldırın"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"Arama geçmişi temizlendi"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"Bildirimler"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"Titreşim"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"Anımsatıcılar için de titret"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"Zil sesini seç"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"Pop-up bildirim"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"Varsayılan hatırlatıcı süresi"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"Varsayılan hatırlatıcı süresi"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,13 @@
     <item msgid="4552962311897985633">"Hiçbir zaman"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"Titreşim"</string>
+    <string name="gadget_title" msgid="939918875331310945">"Takvim"</string>
+    <!-- no translation found for gadget_more_events:one (1813062802420615657) -->
+    <!-- no translation found for gadget_more_events:other (7993512762778431065) -->
+    <!-- no translation found for gadget_no_events (5209110911088302855) -->
+    <skip />
+    <string name="tomorrow" msgid="614434811813859237">"Yarın"</string>
+    <string name="in_progress" msgid="3637799356103489011">"etkinlik devam ediyor"</string>
+    <string name="today" msgid="8577375020252453006">"Bugün"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"<xliff:g id="DOMAIN">%s</xliff:g> aranıyor…"</string>
 </resources>
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
index c2d015d..1fefbcd 100644
--- a/res/values-zh-rCN/arrays.xml
+++ b/res/values-zh-rCN/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 天"</item>
     <item msgid="3390146235648970198">"1 周"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"无"</item>
+    <item msgid="3377199512833209480">"1 分钟"</item>
+    <item msgid="5023205715881387516">"5 分钟"</item>
+    <item msgid="7720870540593319107">"10 分钟"</item>
+    <item msgid="1653841700859180907">"15 分钟"</item>
+    <item msgid="3741629746313107482">"20 分钟"</item>
+    <item msgid="5357458446224446398">"25 分钟"</item>
+    <item msgid="403143749684280703">"30 分钟"</item>
+    <item msgid="5611574008997362948">"45 分钟"</item>
+    <item msgid="30462793717552">"1 小时"</item>
+    <item msgid="119808229352802480">"2 小时"</item>
+    <item msgid="7456742278252094745">"3 小时"</item>
+    <item msgid="5263928189935026559">"12 小时"</item>
+    <item msgid="6451763179435252852">"24 小时"</item>
+    <item msgid="966666856467721784">"2 天"</item>
+    <item msgid="5840973497558794742">"1 周"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"语言区域的默认设置"</item>
+    <item msgid="134027225275475280">"星期六"</item>
+    <item msgid="95029346069903091">"星期日"</item>
+    <item msgid="5840983116375063739">"星期一"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"马绍尔群岛"</item>
     <item msgid="7176569939440505575">"中途岛"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"贝尔格莱德"</item>
     <item msgid="3284290759549286849">"布鲁塞尔"</item>
     <item msgid="3112943938015033049">"萨拉热窝"</item>
-    <item msgid="2184425710639965762">"巴黎"</item>
-    <item msgid="5997630882187580225">"温得和克"</item>
-    <item msgid="3828800493609994208">"西非时间"</item>
-    <item msgid="7527935722014187591">"安曼,约旦"</item>
-    <item msgid="672509109295227133">"雅典,伊斯坦布尔"</item>
-    <item msgid="1835470340349221646">"贝鲁特,黎巴嫩"</item>
-    <item msgid="9006223109403693650">"开罗"</item>
-    <item msgid="2877875673636361825">"赫尔辛基"</item>
-    <item msgid="4528124308590807049">"耶路撒冷"</item>
-    <item msgid="7823856457885290195">"明斯克"</item>
-    <item msgid="8650995074282357197">"哈拉雷"</item>
-    <item msgid="209523815326871285">"巴格达"</item>
-    <item msgid="8077211460729251609">"莫斯科"</item>
-    <item msgid="6103383184232804522">"科威特"</item>
-    <item msgid="8581935265080466099">"内罗毕"</item>
-    <item msgid="1531618843539764728">"德黑兰"</item>
-    <item msgid="141548505188106312">"巴库"</item>
-    <item msgid="5974315340250149692">"第比利斯"</item>
-    <item msgid="3344801680659888795">"埃里温"</item>
-    <item msgid="2537944335416132075">"迪拜"</item>
-    <item msgid="5630432498109110949">"喀布尔"</item>
-    <item msgid="2274762525650993846">"伊斯兰堡,卡拉奇"</item>
-    <item msgid="272153171103339242">"乌拉尔斯克"</item>
-    <item msgid="4757644551253886090">"叶卡捷琳堡"</item>
-    <item msgid="3036512479614493957">"加尔各答"</item>
-    <item msgid="4190271241277312277">"斯里兰卡"</item>
-    <item msgid="4489139826358657866">"加德满都"</item>
-    <item msgid="1195461607876034232">"阿斯塔纳"</item>
-    <item msgid="8777509327730950228">"仰光"</item>
-    <item msgid="3838899720161825998">"克拉斯诺亚尔斯克"</item>
-    <item msgid="6495532062164690437">"曼谷"</item>
-    <item msgid="4910549870048384487">"北京"</item>
-    <item msgid="7174954011332328153">"香港"</item>
-    <item msgid="415034182139280334">"伊尔库茨克"</item>
-    <item msgid="3407263784503834649">"吉隆坡"</item>
-    <item msgid="6547945295249807755">"珀斯"</item>
-    <item msgid="4571776847411430193">"台北"</item>
-    <item msgid="3043691515163887690">"首尔"</item>
-    <item msgid="6369124969606372122">"东京,大阪府"</item>
-    <item msgid="8317842783685169193">"雅库茨克"</item>
-    <item msgid="6993206457323990990">"阿德莱德"</item>
-    <item msgid="2431970257749452312">"达尔文"</item>
-    <item msgid="2709489188606506801">"布里斯班"</item>
-    <item msgid="6832511138206913044">"霍巴特"</item>
-    <item msgid="6933429790996727761">"悉尼,堪培拉"</item>
-    <item msgid="4546625974877459074">"海参崴"</item>
-    <item msgid="7421491250467489614">"关岛"</item>
-    <item msgid="3450367738093440984">"马加丹"</item>
-    <item msgid="5046540868552082971">"奥克兰"</item>
-    <item msgid="481903191189494072">"斐济"</item>
-    <item msgid="1926932394586102998">"汤加"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"无"</item>
-    <item msgid="3377199512833209480">"1 分钟"</item>
-    <item msgid="5023205715881387516">"5 分钟"</item>
-    <item msgid="7720870540593319107">"10 分钟"</item>
-    <item msgid="1653841700859180907">"15 分钟"</item>
-    <item msgid="3741629746313107482">"20 分钟"</item>
-    <item msgid="5357458446224446398">"25 分钟"</item>
-    <item msgid="403143749684280703">"30 分钟"</item>
-    <item msgid="5611574008997362948">"45 分钟"</item>
-    <item msgid="30462793717552">"1 小时"</item>
-    <item msgid="119808229352802480">"2 小时"</item>
-    <item msgid="7456742278252094745">"3 小时"</item>
-    <item msgid="5263928189935026559">"12 小时"</item>
-    <item msgid="6451763179435252852">"24 小时"</item>
-    <item msgid="966666856467721784">"2 天"</item>
-    <item msgid="5840973497558794742">"1 周"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"警报"</item>
-    <item msgid="8723674378160518790">"状态栏通知"</item>
-    <item msgid="8433736231022186661">"关闭"</item>
+    <item msgid="1746966478800108111">"温得和克"</item>
+    <item msgid="7008598576103662879">"西非时间"</item>
+    <item msgid="1358916275855835758">"安曼,约旦"</item>
+    <item msgid="5900185968201011015">"雅典,伊斯坦布尔"</item>
+    <item msgid="4975304496845689895">"贝鲁特,黎巴嫩"</item>
+    <item msgid="4099790960681254059">"开罗"</item>
+    <item msgid="4174072690298729061">"赫尔辛基"</item>
+    <item msgid="5233437715028921599">"耶路撒冷"</item>
+    <item msgid="612137838544847325">"明斯克"</item>
+    <item msgid="385832033162858704">"哈拉雷"</item>
+    <item msgid="1439592855284837653">"巴格达"</item>
+    <item msgid="3703352987270492256">"莫斯科"</item>
+    <item msgid="1657996636394546903">"科威特"</item>
+    <item msgid="3719675800096223903">"内罗毕"</item>
+    <item msgid="4216389209911081345">"德黑兰"</item>
+    <item msgid="4949871918709805735">"巴库"</item>
+    <item msgid="2863183321532922311">"第比利斯"</item>
+    <item msgid="1943529325618264999">"埃里温"</item>
+    <item msgid="5094507376721687886">"迪拜"</item>
+    <item msgid="2849654571454095226">"喀布尔"</item>
+    <item msgid="3641915655045056824">"伊斯兰堡,卡拉奇"</item>
+    <item msgid="5795351343828254400">"乌拉尔斯克"</item>
+    <item msgid="8779467771115356898">"叶卡捷琳堡"</item>
+    <item msgid="297412491658718331">"加尔各答"</item>
+    <item msgid="2290076787008042202">"斯里兰卡"</item>
+    <item msgid="4119703758417825749">"加德满都"</item>
+    <item msgid="9161760299912900568">"阿斯塔纳"</item>
+    <item msgid="5753596907402377327">"仰光"</item>
+    <item msgid="6275146302340764856">"克拉斯诺亚尔斯克"</item>
+    <item msgid="2385588555356900033">"曼谷"</item>
+    <item msgid="7651147941145227340">"北京"</item>
+    <item msgid="7831696303531874014">"香港"</item>
+    <item msgid="7943498021949728203">"伊尔库茨克"</item>
+    <item msgid="707009198021550118">"吉隆坡"</item>
+    <item msgid="4268864549735629220">"珀斯"</item>
+    <item msgid="4584239166170514795">"台北"</item>
+    <item msgid="4972387553080120086">"首尔"</item>
+    <item msgid="5749349714875345358">"东京,大阪府"</item>
+    <item msgid="4435870687083770252">"雅库茨克"</item>
+    <item msgid="4857952326324510625">"阿德莱德"</item>
+    <item msgid="7802545626741434274">"达尔文"</item>
+    <item msgid="7184691104734480971">"布里斯班"</item>
+    <item msgid="7795651165061255022">"霍巴特"</item>
+    <item msgid="1858052331367859514">"悉尼,堪培拉"</item>
+    <item msgid="4396794682654884440">"海参崴"</item>
+    <item msgid="4152655496917506653">"关岛"</item>
+    <item msgid="3608508135351982746">"马加丹"</item>
+    <item msgid="722180563708274099">"奥克兰"</item>
+    <item msgid="1736037231626139044">"斐济"</item>
+    <item msgid="8187746821081424875">"汤加"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"忙碌"</item>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index f8be629..309a43e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"日历"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"多合一日历"</string>
     <string name="what_label" msgid="1933578391985810049">"内容"</string>
     <string name="where_label" msgid="3472482206065595118">"地点"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"时区"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"时区"</string>
     <string name="attendees_label" msgid="2971281641565729725">"邀请对象"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"重复"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 天"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> 天"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"刷新"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"显示日程"</string>
     <string name="show_day_view" msgid="5782512782605466016">"显示日期"</string>
     <string name="agenda_view" msgid="5566084759423843245">"日程"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"今天"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"日历"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"设置"</string>
+    <string name="search" msgid="550656004486017426">"搜索"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(以及 <xliff:g id="MORE_COUNT">%d</xliff:g> 个其他活动...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"日历"</string>
     <string name="synced_visible" msgid="1406744353749335651">"已同步,可显示"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"已同步,不显示"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"未同步,不显示"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"活动详情"</string>
     <string name="hint_what" msgid="709155115005044531">"活动名称"</string>
     <string name="hint_where" msgid="3116239630502213241">"活动地点"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"日历"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"显示更多选项"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"隐藏额外选项"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"显示全部选择 &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"显示全部选择"</string>
     <string name="description_label" msgid="7193475046717505855">"说明"</string>
     <string name="presence_label" msgid="6169724148441175862">"将我的状态显示为"</string>
     <string name="privacy_label" msgid="9215031965259597335">"隐私"</string>
     <string name="reminders_label" msgid="8345054160145333166">"提醒"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"无日历"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"您没有日历。"</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"开始日期"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"开始时间"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"结束日期"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"结束时间"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"提醒"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"删除提醒"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"日历:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"组织者:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"本地时区:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"参加吗?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"今天(<xliff:g id="DATE">%1$s</xliff:g>)"</string>
     <string name="loading" msgid="3772533493214331230">"正在载入..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"显示<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>以后发生的活动。点按可查看更多活动。"</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"显示<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>之前发生的活动。点按可查看更多活动。"</string>
+    <string name="show_older_events" msgid="6819097859184398737">"显示自 <xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>以来的活动。触摸即可搜索更早的活动。"</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"显示 <xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>之前的活动。触摸即可搜索之后的活动。"</string>
+    <string name="search_title" msgid="8898015280250716254">"搜索我的日历"</string>
     <string name="num_events" msgid="3351672964607162257">"活动数"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"编辑活动"</string>
     <string name="delete_label" msgid="874742811918030739">"删除"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"删除活动"</string>
     <string name="save_label" msgid="2133599104834432589">"完成"</string>
-    <string name="discard_label" msgid="4510607554910139220">"还原"</string>
+    <string name="discard_label" msgid="2394623883289782541">"取消"</string>
     <string name="import_label" msgid="6993796574741477222">"导入"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"全部延迟"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"全部关闭"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"将会删除此活动。"</string>
     <string name="delete_title" msgid="5143743507524995383">"删除"</string>
     <string name="change_response_title" msgid="7704714776070974984">"更改答复"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"设置"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"日历视图设置"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"提醒设置"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"提醒设置"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"隐藏已拒绝的活动"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"设置警报和通知"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"警报和通知"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"一周开始日"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"一周开始日"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"清除搜索记录"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"删除您已执行的所有搜索"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"搜索记录已清除"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"通知"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"振动"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"收到提醒时也振动"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"选择铃声"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"弹出式通知"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"默认提醒时间"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"默认提醒时间"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"一律不"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"振动"</string>
+    <string name="gadget_title" msgid="939918875331310945">"日历"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"另外 1 个活动"</item>
+    <item quantity="other" msgid="7993512762778431065">"另外 <xliff:g id="NUMBER">%d</xliff:g> 个活动"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"近期没有日历活动"</string>
+    <string name="tomorrow" msgid="614434811813859237">"明天"</string>
+    <string name="in_progress" msgid="3637799356103489011">"正在进行"</string>
+    <string name="today" msgid="8577375020252453006">"今天"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"正在搜索 <xliff:g id="DOMAIN">%s</xliff:g>…"</string>
 </resources>
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
index 235c801..ce01467 100644
--- a/res/values-zh-rTW/arrays.xml
+++ b/res/values-zh-rTW/arrays.xml
@@ -33,6 +33,30 @@
     <item msgid="6908283876728494719">"2 天"</item>
     <item msgid="3390146235648970198">"1 週"</item>
   </string-array>
+  <string-array name="preferences_default_reminder_labels">
+    <item msgid="7495163916242649023">"無"</item>
+    <item msgid="3377199512833209480">"1 分鐘"</item>
+    <item msgid="5023205715881387516">"5 分鐘"</item>
+    <item msgid="7720870540593319107">"10 分鐘"</item>
+    <item msgid="1653841700859180907">"15 分鐘"</item>
+    <item msgid="3741629746313107482">"20 分鐘"</item>
+    <item msgid="5357458446224446398">"25 分鐘"</item>
+    <item msgid="403143749684280703">"30 分鐘"</item>
+    <item msgid="5611574008997362948">"45 分鐘"</item>
+    <item msgid="30462793717552">"1 小時"</item>
+    <item msgid="119808229352802480">"2 小時"</item>
+    <item msgid="7456742278252094745">"3 小時"</item>
+    <item msgid="5263928189935026559">"12 小時"</item>
+    <item msgid="6451763179435252852">"24 小時"</item>
+    <item msgid="966666856467721784">"2 天"</item>
+    <item msgid="5840973497558794742">"1 週"</item>
+  </string-array>
+  <string-array name="preferences_week_start_day_labels">
+    <item msgid="986150274035512339">"地區設定預設值"</item>
+    <item msgid="134027225275475280">"週六"</item>
+    <item msgid="95029346069903091">"週日"</item>
+    <item msgid="5840983116375063739">"週一"</item>
+  </string-array>
   <string-array name="timezone_labels">
     <item msgid="6833649526258274889">"馬紹爾群島"</item>
     <item msgid="7176569939440505575">"中途島"</item>
@@ -67,80 +91,56 @@
     <item msgid="3665952257850630547">"貝爾格勒"</item>
     <item msgid="3284290759549286849">"布魯塞爾"</item>
     <item msgid="3112943938015033049">"塞拉耶佛"</item>
-    <item msgid="2184425710639965762">"巴黎"</item>
-    <item msgid="5997630882187580225">"溫特和克"</item>
-    <item msgid="3828800493609994208">"非洲西部時間"</item>
-    <item msgid="7527935722014187591">"安曼、約旦"</item>
-    <item msgid="672509109295227133">"雅典、伊斯坦堡"</item>
-    <item msgid="1835470340349221646">"貝魯特、黎巴嫩"</item>
-    <item msgid="9006223109403693650">"開羅"</item>
-    <item msgid="2877875673636361825">"赫爾辛基"</item>
-    <item msgid="4528124308590807049">"耶路撒冷"</item>
-    <item msgid="7823856457885290195">"明斯克"</item>
-    <item msgid="8650995074282357197">"哈拉雷"</item>
-    <item msgid="209523815326871285">"巴格達"</item>
-    <item msgid="8077211460729251609">"莫斯科"</item>
-    <item msgid="6103383184232804522">"科威特"</item>
-    <item msgid="8581935265080466099">"奈洛比"</item>
-    <item msgid="1531618843539764728">"德黑蘭"</item>
-    <item msgid="141548505188106312">"巴庫"</item>
-    <item msgid="5974315340250149692">"第比利斯"</item>
-    <item msgid="3344801680659888795">"埃里溫"</item>
-    <item msgid="2537944335416132075">"杜拜"</item>
-    <item msgid="5630432498109110949">"喀布爾"</item>
-    <item msgid="2274762525650993846">"伊斯蘭馬巴德、喀拉蚩"</item>
-    <item msgid="272153171103339242">"烏拉爾"</item>
-    <item msgid="4757644551253886090">"凱薩琳堡"</item>
-    <item msgid="3036512479614493957">"加爾各答"</item>
-    <item msgid="4190271241277312277">"斯里蘭卡"</item>
-    <item msgid="4489139826358657866">"加德滿都"</item>
-    <item msgid="1195461607876034232">"阿斯塔納"</item>
-    <item msgid="8777509327730950228">"仰光"</item>
-    <item msgid="3838899720161825998">"克拉斯諾亞爾斯克"</item>
-    <item msgid="6495532062164690437">"曼谷"</item>
-    <item msgid="4910549870048384487">"北京"</item>
-    <item msgid="7174954011332328153">"香港"</item>
-    <item msgid="415034182139280334">"伊爾庫次克"</item>
-    <item msgid="3407263784503834649">"吉隆坡"</item>
-    <item msgid="6547945295249807755">"伯斯"</item>
-    <item msgid="4571776847411430193">"台北"</item>
-    <item msgid="3043691515163887690">"首爾"</item>
-    <item msgid="6369124969606372122">"東京、大阪"</item>
-    <item msgid="8317842783685169193">"亞庫次克"</item>
-    <item msgid="6993206457323990990">"阿得雷德"</item>
-    <item msgid="2431970257749452312">"達爾文"</item>
-    <item msgid="2709489188606506801">"布里斯本"</item>
-    <item msgid="6832511138206913044">"霍巴特"</item>
-    <item msgid="6933429790996727761">"雪梨、坎培拉"</item>
-    <item msgid="4546625974877459074">"海參威"</item>
-    <item msgid="7421491250467489614">"關島"</item>
-    <item msgid="3450367738093440984">"馬加丹"</item>
-    <item msgid="5046540868552082971">"奧克蘭"</item>
-    <item msgid="481903191189494072">"斐濟"</item>
-    <item msgid="1926932394586102998">"東加"</item>
-  </string-array>
-  <string-array name="preferences_default_reminder_labels">
-    <item msgid="7495163916242649023">"無"</item>
-    <item msgid="3377199512833209480">"1 分鐘"</item>
-    <item msgid="5023205715881387516">"5 分鐘"</item>
-    <item msgid="7720870540593319107">"10 分鐘"</item>
-    <item msgid="1653841700859180907">"15 分鐘"</item>
-    <item msgid="3741629746313107482">"20 分鐘"</item>
-    <item msgid="5357458446224446398">"25 分鐘"</item>
-    <item msgid="403143749684280703">"30 分鐘"</item>
-    <item msgid="5611574008997362948">"45 分鐘"</item>
-    <item msgid="30462793717552">"1 小時"</item>
-    <item msgid="119808229352802480">"2 小時"</item>
-    <item msgid="7456742278252094745">"3 小時"</item>
-    <item msgid="5263928189935026559">"12 小時"</item>
-    <item msgid="6451763179435252852">"24 小時"</item>
-    <item msgid="966666856467721784">"2 天"</item>
-    <item msgid="5840973497558794742">"1 週"</item>
-  </string-array>
-  <string-array name="preferences_alert_type_labels">
-    <item msgid="2724242028978664229">"警示"</item>
-    <item msgid="8723674378160518790">"狀態列通知"</item>
-    <item msgid="8433736231022186661">"關閉"</item>
+    <item msgid="1746966478800108111">"溫特和克"</item>
+    <item msgid="7008598576103662879">"非洲西部時間"</item>
+    <item msgid="1358916275855835758">"安曼、約旦"</item>
+    <item msgid="5900185968201011015">"雅典、伊斯坦堡"</item>
+    <item msgid="4975304496845689895">"貝魯特、黎巴嫩"</item>
+    <item msgid="4099790960681254059">"開羅"</item>
+    <item msgid="4174072690298729061">"赫爾辛基"</item>
+    <item msgid="5233437715028921599">"耶路撒冷"</item>
+    <item msgid="612137838544847325">"明斯克"</item>
+    <item msgid="385832033162858704">"哈拉雷"</item>
+    <item msgid="1439592855284837653">"巴格達"</item>
+    <item msgid="3703352987270492256">"莫斯科"</item>
+    <item msgid="1657996636394546903">"科威特"</item>
+    <item msgid="3719675800096223903">"奈洛比"</item>
+    <item msgid="4216389209911081345">"德黑蘭"</item>
+    <item msgid="4949871918709805735">"巴庫"</item>
+    <item msgid="2863183321532922311">"第比利斯"</item>
+    <item msgid="1943529325618264999">"埃里溫"</item>
+    <item msgid="5094507376721687886">"杜拜"</item>
+    <item msgid="2849654571454095226">"喀布爾"</item>
+    <item msgid="3641915655045056824">"伊斯蘭馬巴德、喀拉蚩"</item>
+    <item msgid="5795351343828254400">"烏拉爾"</item>
+    <item msgid="8779467771115356898">"凱薩琳堡"</item>
+    <item msgid="297412491658718331">"加爾各答"</item>
+    <item msgid="2290076787008042202">"斯里蘭卡"</item>
+    <item msgid="4119703758417825749">"加德滿都"</item>
+    <item msgid="9161760299912900568">"阿斯塔納"</item>
+    <item msgid="5753596907402377327">"仰光"</item>
+    <item msgid="6275146302340764856">"克拉斯諾亞爾斯克"</item>
+    <item msgid="2385588555356900033">"曼谷"</item>
+    <item msgid="7651147941145227340">"北京"</item>
+    <item msgid="7831696303531874014">"香港"</item>
+    <item msgid="7943498021949728203">"伊爾庫次克"</item>
+    <item msgid="707009198021550118">"吉隆坡"</item>
+    <item msgid="4268864549735629220">"伯斯"</item>
+    <item msgid="4584239166170514795">"台北"</item>
+    <item msgid="4972387553080120086">"首爾"</item>
+    <item msgid="5749349714875345358">"東京、大阪"</item>
+    <item msgid="4435870687083770252">"亞庫次克"</item>
+    <item msgid="4857952326324510625">"阿得雷德"</item>
+    <item msgid="7802545626741434274">"達爾文"</item>
+    <item msgid="7184691104734480971">"布里斯本"</item>
+    <item msgid="7795651165061255022">"霍巴特"</item>
+    <item msgid="1858052331367859514">"雪梨、坎培拉"</item>
+    <item msgid="4396794682654884440">"海參威"</item>
+    <item msgid="4152655496917506653">"關島"</item>
+    <item msgid="3608508135351982746">"馬加丹"</item>
+    <item msgid="722180563708274099">"奧克蘭"</item>
+    <item msgid="1736037231626139044">"斐濟"</item>
+    <item msgid="8187746821081424875">"東加"</item>
   </string-array>
   <string-array name="availability">
     <item msgid="454869065893453189">"忙碌"</item>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index fa5b5fa..b08e97e 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="5452708708398908131">"Google 日曆"</string>
+    <string name="app_aio_label" msgid="3058561424021284581">"萬用日曆"</string>
     <string name="what_label" msgid="1933578391985810049">"主題"</string>
     <string name="where_label" msgid="3472482206065595118">"地點"</string>
-    <string name="timezone_label" msgid="3988530993300431650">"時區"</string>
+    <string name="timezone_label" msgid="2030635806231207425">"時區"</string>
     <string name="attendees_label" msgid="2971281641565729725">"邀請對象"</string>
     <string name="response_label" msgid="5732943469653254290">"<xliff:g id="RESPONSE_TYPE">%s</xliff:g> (<xliff:g id="GUEST_COUNT">%d</xliff:g>)"</string>
     <string name="repeats_label" msgid="7414023871434593196">"重複頻率"</string>
@@ -40,6 +41,7 @@
     <item quantity="one" msgid="5988137255732166573">"1 天"</item>
     <item quantity="other" msgid="1709378992389618261">"<xliff:g id="COUNT">%d</xliff:g> 天"</item>
   </plurals>
+    <string name="calendar_refresh" msgid="6172731193680500025">"重新整理"</string>
     <string name="show_agenda_view" msgid="7971854300981408347">"顯示待辦事項"</string>
     <string name="show_day_view" msgid="5782512782605466016">"顯示天"</string>
     <string name="agenda_view" msgid="5566084759423843245">"待辦事項"</string>
@@ -53,11 +55,20 @@
     <string name="goto_today" msgid="9162136434410891230">"今日"</string>
     <string name="menu_select_calendars" msgid="6200145996783123958">"日曆"</string>
     <string name="menu_preferences" msgid="9120238479503233925">"設定"</string>
+    <string name="search" msgid="550656004486017426">"搜尋"</string>
     <string name="plus_N_more" msgid="2527895721728080055">"(還有 <xliff:g id="MORE_COUNT">%d</xliff:g> 個...)"</string>
     <string name="calendars_title" msgid="4046980972626276968">"日曆"</string>
     <string name="synced_visible" msgid="1406744353749335651">"已同步處理;正常顯示"</string>
     <string name="synced_not_visible" msgid="54032655953110609">"已同步處理;未顯示"</string>
     <string name="not_synced_not_visible" msgid="7178879061972059481">"未同步處理;未顯示"</string>
+    <!-- no translation found for synced (7913940305983383493) -->
+    <skip />
+    <!-- no translation found for not_synced (4275114941801829419) -->
+    <skip />
+    <!-- no translation found for acct_not_synced (4276198677633672868) -->
+    <skip />
+    <!-- no translation found for accounts (849401841083180306) -->
+    <skip />
     <string name="event_edit_title" msgid="8487120407086532444">"活動詳細資訊"</string>
     <string name="hint_what" msgid="709155115005044531">"活動名稱"</string>
     <string name="hint_where" msgid="3116239630502213241">"活動地點"</string>
@@ -79,33 +90,31 @@
     <string name="edit_event_calendar_label" msgid="4556498359505856900">"日曆"</string>
     <string name="edit_event_show_extra_options" msgid="2325854287823989650">"顯示少用選項"</string>
     <string name="edit_event_hide_extra_options" msgid="4019082307326855513">"隱藏少用選項"</string>
-    <string name="edit_event_show_all" msgid="6107214899021208078">"全部顯示 &gt;"</string>
+    <string name="edit_event_show_all" msgid="4558049350756058301">"全部顯示"</string>
     <string name="description_label" msgid="7193475046717505855">"說明"</string>
     <string name="presence_label" msgid="6169724148441175862">"將我的狀態顯示為"</string>
     <string name="privacy_label" msgid="9215031965259597335">"隱私權"</string>
     <string name="reminders_label" msgid="8345054160145333166">"提醒"</string>
     <string name="no_syncable_calendars" msgid="7019488867045436129">"無日曆"</string>
-    <string name="no_calendars_found" msgid="755379468136462058">"您沒有日曆。"</string>
-    <string name="content_description_edit_event_from_date" msgid="4180292119507352306">"開始日期"</string>
-    <string name="content_description_edit_event_from_time" msgid="6740212065246258103">"開始時間"</string>
-    <string name="content_description_edit_event_to_date" msgid="2731432809487159262">"結束日期"</string>
-    <string name="content_description_edit_event_to_time" msgid="5266222191518214928">"結束時間"</string>
-    <string name="content_description_edit_event_reminders" msgid="62776037465727715">"提醒類型"</string>
-    <string name="content_description_edit_event_remove_reminder" msgid="877549621029991866">"移除提醒"</string>
+    <!-- no translation found for no_calendars_found (51300139477091968) -->
+    <skip />
+    <!-- no translation found for add_account (1669072099756774952) -->
+    <skip />
     <string name="view_event_calendar_label" msgid="6126231656338241140">"日曆:"</string>
     <string name="view_event_organizer_label" msgid="2753151795647402277">"主辦人:"</string>
     <string name="view_event_timezone_label" msgid="4757495486359041757">"當地時區:"</string>
     <string name="view_event_response_label" msgid="8382281658458798329">"是否參加?"</string>
     <string name="agenda_today" msgid="6489447319363439068">"今天 (<xliff:g id="DATE">%1$s</xliff:g>)"</string>
     <string name="loading" msgid="3772533493214331230">"載入中..."</string>
-    <string name="show_older_events" msgid="4031960000704544741">"顯示「<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>」之後的活動,輕按以查看更多活動。"</string>
-    <string name="show_newer_events" msgid="2647889653936094369">"顯示「<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>」之前的活動,輕按以查看更多活動。"</string>
+    <string name="show_older_events" msgid="6819097859184398737">"顯示「<xliff:g id="OLDEST_SEARCH_RANGE">%1$s</xliff:g>」之後的活動。輕觸一下,即可搜尋之前的活動。"</string>
+    <string name="show_newer_events" msgid="3434093781980113106">"顯示「<xliff:g id="NEWEST_SEARCH_RANGE">%1$s</xliff:g>」之前的活動。輕觸一下,即可搜尋之後的活動。"</string>
+    <string name="search_title" msgid="8898015280250716254">"搜尋我的日曆"</string>
     <string name="num_events" msgid="3351672964607162257">"活動數"</string>
     <string name="edit_event_label" msgid="2900418236819088363">"編輯活動"</string>
     <string name="delete_label" msgid="874742811918030739">"刪除"</string>
     <string name="delete_event_label" msgid="3738436215987360463">"刪除活動"</string>
     <string name="save_label" msgid="2133599104834432589">"完成"</string>
-    <string name="discard_label" msgid="4510607554910139220">"復原"</string>
+    <string name="discard_label" msgid="2394623883289782541">"取消"</string>
     <string name="import_label" msgid="6993796574741477222">"匯入"</string>
     <string name="snooze_all_label" msgid="4994488813124409077">"全部延遲"</string>
     <string name="dismiss_all_label" msgid="1834400025868091301">"全部關閉"</string>
@@ -125,15 +134,24 @@
     <string name="delete_this_event_title" msgid="8738491083082780492">"刪除此活動?"</string>
     <string name="delete_title" msgid="5143743507524995383">"刪除"</string>
     <string name="change_response_title" msgid="7704714776070974984">"變更回應"</string>
+    <!-- no translation found for menu_general_preferences (4057784477122524106) -->
+    <skip />
+    <!-- no translation found for menu_about_preferences (160474554936812478) -->
+    <skip />
     <string name="preferences_title" msgid="8045855493262131773">"設定"</string>
     <string name="preferences_general_title" msgid="2069075513760573117">"日曆檢視設定"</string>
-    <string name="preferences_alerts_title" msgid="6511227171114210908">"提醒設定"</string>
+    <string name="preferences_reminder_title" msgid="6940022527071973010">"提醒設定"</string>
     <string name="preferences_hide_declined_title" msgid="1849295943669890034">"隱藏已拒絕的活動"</string>
-    <string name="preferences_alerts_type_title" msgid="6934441966757833908">"設定警示及通知"</string>
-    <string name="preferences_alerts_type_dialog" msgid="2195667763429994271">"警示及通知"</string>
+    <string name="preferences_week_start_day_title" msgid="4619489296444901622">"一週起始日"</string>
+    <string name="preferences_week_start_day_dialog" msgid="5181634128884089113">"一週起始日"</string>
+    <string name="preferences_clear_search_history_title" msgid="6619916370215282982">"清除搜尋記錄"</string>
+    <string name="preferences_clear_search_history_summary" msgid="5813077357221990">"移除所有您曾執行的搜尋記錄"</string>
+    <string name="search_history_cleared" msgid="7317062958300188595">"已清除搜尋記錄"</string>
+    <string name="preferences_alerts_title" msgid="6636088008103904020">"通知"</string>
     <string name="preferences_alerts_vibrateWhen_title" msgid="8731372580674292759">"震動"</string>
     <string name="preferences_alerts_vibrateWhen_summary" msgid="2809567649955441757">"同時震動提醒"</string>
     <string name="preferences_alerts_ringtone_title" msgid="7565118268120729644">"選取鈴聲"</string>
+    <string name="preferences_alerts_popup_title" msgid="6643468203670475296">"彈出式通知"</string>
     <string name="preferences_default_reminder_title" msgid="5188861433342733905">"預設提醒時間"</string>
     <string name="preferences_default_reminder_dialog" msgid="2490348857239323412">"預設提醒時間"</string>
     <string name="preferences_default_reminder_default" msgid="2903884416749959435">"10"</string>
@@ -148,4 +166,14 @@
     <item msgid="4552962311897985633">"永遠不要"</item>
   </string-array>
     <string name="prefDialogTitle_vibrateWhen" msgid="8995274609647451109">"震動"</string>
+    <string name="gadget_title" msgid="939918875331310945">"日曆"</string>
+  <plurals name="gadget_more_events">
+    <item quantity="one" msgid="1813062802420615657">"還有 1 個活動"</item>
+    <item quantity="other" msgid="7993512762778431065">"還有 <xliff:g id="NUMBER">%d</xliff:g> 個活動"</item>
+  </plurals>
+    <string name="gadget_no_events" msgid="5209110911088302855">"沒有即將開始的日曆活動"</string>
+    <string name="tomorrow" msgid="614434811813859237">"明天"</string>
+    <string name="in_progress" msgid="3637799356103489011">"進行中"</string>
+    <string name="today" msgid="8577375020252453006">"今天"</string>
+    <string name="directory_searching_fmt" msgid="1232037509095606687">"正在搜尋 <xliff:g id="DOMAIN">%s</xliff:g>..."</string>
 </resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index f2d2c0c..73afd77 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -54,8 +54,61 @@
         <item>"10080"</item>
     </string-array>
 
+    <string-array name="preferences_default_reminder_labels">
+        <item>None</item>
+        <item>1 minute</item>
+        <item>5 minutes</item>
+        <item>10 minutes</item>
+        <item>15 minutes</item>
+        <item>20 minutes</item>
+        <item>25 minutes</item>
+        <item>30 minutes</item>
+        <item>45 minutes</item>
+        <item>1 hour</item>
+        <item>2 hours</item>
+        <item>3 hours</item>
+        <item>12 hours</item>
+        <item>24 hours</item>
+        <item>2 days</item>
+        <item>1 week</item>
+    </string-array>
+
+    <string-array name="preferences_default_reminder_values" translatable="false">
+        <item>"0"</item>
+        <item>"1"</item>
+        <item>"5"</item>
+        <item>"10"</item>
+        <item>"15"</item>
+        <item>"20"</item>
+        <item>"25"</item>
+        <item>"30"</item>
+        <item>"45"</item>
+        <item>"60"</item>
+        <item>"120"</item>
+        <item>"180"</item>
+        <item>"720"</item>
+        <item>"1440"</item>
+        <item>"2880"</item>
+        <item>"10080"</item>
+    </string-array>
+
+    <string-array name="preferences_week_start_day_labels">
+        <item>Locale default</item>
+        <item>Saturday</item>
+        <item>Sunday</item>
+        <item>Monday</item>
+    </string-array>
+
+    <string-array name="preferences_week_start_day_values" translatable="false">
+        <item>"-1"</item>
+        <item>"7"</item>
+        <item>"1"</item>
+        <item>"2"</item>
+    </string-array>
+
     <!-- Choices for timezones, must be kept in sync with timezone_values. This
-         list was taken from package/apps/Settings/res/xml/timezones.xml -->
+         list was taken from package/apps/Settings/res/xml/timezones.xml
+         [CHAR LIMIT=20] -->
     <string-array name="timezone_labels">
         <item>"Marshall Islands"</item>
         <item>"Midway Island"</item>
@@ -90,7 +143,6 @@
         <item>"Belgrade"</item>
         <item>"Brussels"</item>
         <item>"Sarajevo"</item>
-        <item>"Paris"</item>
         <item>"Windhoek"</item>
         <item>"W. Africa Time"</item>
         <item>"Amman, Jordan"</item>
@@ -178,7 +230,6 @@
         <item>"Europe/Belgrade"</item>
         <item>"Europe/Brussels"</item>
         <item>"Europe/Sarajevo"</item>
-        <item>"Europe/Paris"</item>
         <item>"Africa/Windhoek"</item>
         <item>"Africa/Brazzaville"</item>
         <item>"Asia/Amman"</item>
@@ -231,56 +282,6 @@
         <item>"Pacific/Tongatapu"</item>
     </string-array>
 
-    <string-array name="preferences_default_reminder_labels">
-        <item>None</item>
-        <item>1 minute</item>
-        <item>5 minutes</item>
-        <item>10 minutes</item>
-        <item>15 minutes</item>
-        <item>20 minutes</item>
-        <item>25 minutes</item>
-        <item>30 minutes</item>
-        <item>45 minutes</item>
-        <item>1 hour</item>
-        <item>2 hours</item>
-        <item>3 hours</item>
-        <item>12 hours</item>
-        <item>24 hours</item>
-        <item>2 days</item>
-        <item>1 week</item>
-    </string-array>
-
-    <string-array name="preferences_default_reminder_values" translatable="false">
-        <item>"0"</item>
-        <item>"1"</item>
-        <item>"5"</item>
-        <item>"10"</item>
-        <item>"15"</item>
-        <item>"20"</item>
-        <item>"25"</item>
-        <item>"30"</item>
-        <item>"45"</item>
-        <item>"60"</item>
-        <item>"120"</item>
-        <item>"180"</item>
-        <item>"720"</item>
-        <item>"1440"</item>
-        <item>"2880"</item>
-        <item>"10080"</item>
-    </string-array>
-
-    <string-array name="preferences_alert_type_labels">
-        <item>Alert</item>
-        <item>Status bar notification</item>
-        <item>Off</item>
-    </string-array>
-
-    <string-array name="preferences_alert_type_values" translatable="false">
-        <item>"0"</item>
-        <item>"1"</item>
-        <item>"2"</item>
-    </string-array>
-
     <string-array name="availability">
         <item>Busy</item>
         <item>Available</item>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 03124bf..a77373d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -20,52 +20,60 @@
 <resources>
     <color name="event_background">#ffc3d9ff</color>
     <color name="panel_text_foreground">#ffffffff</color>
-    <color name="daynames_background">#FFdedede</color>
+    <color name="daynames_background">#FFDDDDDD</color>
     <color name="daynames_text_color">#ff000000</color>
-    <color name="sunday_text_color">#ff757575</color>
-    <color name="saturday_text_color">#ff757575</color>
+    <color name="sunday_text_color">#ff446688</color>
+    <color name="saturday_text_color">#ff446688</color>
     <color name="calendar_event_selected_text_color">#ff000000</color>
     <color name="calendar_event_text_color">#ffffffff</color>
     <color name="calendar_owner_text_color">#ffbebebe</color>
 
-    <color name="calendar_all_day_background">#ffdedede</color>
-    <color name="week_saturday">#ff757575</color>
-    <color name="week_sunday">#ff757575</color>
+    <color name="calendar_all_day_background">#ffDDDDDD</color>
+    <color name="week_saturday">#ff446688</color>
+    <color name="week_sunday">#ff446688</color>
     <color name="calendar_hour_label">#ff000000</color>
     <color name="calendar_ampm_label">#ff000000</color>
-    <color name="calendar_hour_background">#ffdedede</color>
+    <color name="calendar_hour_background">#ffDDDDDD</color>
     <color name="calendar_hour_selected">#ffB0B0B0</color>
-    <color name="calendar_date_banner_background">#ffdedede</color>
+    <color name="calendar_date_banner_background">#FFDDDDDD</color>
     <color name="calendar_date_selected">#ffB0B0B0</color>
     <color name="calendar_date_banner_text_color">#ff000000</color>
     <color name="calendar_grid_area_background">#ffffffff</color>
-    <color name="calendar_grid_area_selected">#ffefede9</color>
+    <color name="calendar_grid_area_selected">#ffffffcc</color>
     <color name="calendar_grid_line_horizontal_color">#ffdddddd</color>
-    <color name="calendar_grid_line_vertical_color">#ffdddddd</color>
+    <color name="calendar_grid_line_vertical_color">#ff808080</color>
     <color name="calendar_grid_line_highlight_color">#ff707070</color>
     <color name="selection">#ffffaa00</color>
-    <color name="pressed">#ffefede9</color>
+    <color name="pressed">#fffab400</color>
 
     <color name="month_day_number">#ff404040</color>
     <color name="month_saturday">#ff404040</color>
     <color name="month_sunday">#ff404040</color>
     <color name="month_today_number">#ffffffff</color>
-    <color name="month_today_bgcolor">#FF888888</color>
-    <color name="month_other_month">#ffececec</color>
-    <color name="month_other_month_day_number">#ffb7b7b7</color>
+    <color name="month_grid_lines">#ff595959</color>
+    <color name="month_other_month_day_number">#ffa0a0a0</color>
     <color name="month_other_month_banner">#80595959</color>
-    <color name="month_week_banner">#ffdedede</color>
+    <color name="month_week_banner">#FFDDDDDD</color>
     <color name="month_busybits">#FF6090F0</color>
-    <color name="month_bgcolor">#ffdedede</color>
+    <color name="month_other_bgcolor">#FFDDDDDD</color>
+    <color name="month_today_bgcolor">#FF888888</color>
+    <color name="month_bgcolor">#FFFFFFFF</color>
 
     <color name="event_center">#ff6bd697</color>
 
-    <color name="current_time_marker">#aacababa</color>
-    <color name="current_time_line">#aaff0033</color>
+    <color name="current_time_marker">#aaff0033</color>
     <color name="current_time_marker_border">#aaffffff</color>
 
     <color name="agenda_day_bar_color">#c8c8c8</color>
 
     <drawable name="agenda_item_declined">#88ffffff</drawable>
+
+    <color name="appwidget_date">#ff000000</color>
+    <color name="appwidget_title">#ff000000</color>
+    <color name="appwidget_when">#ff000000</color>
+    <color name="appwidget_where">#ff666666</color>
+    <color name="appwidget_conflict">#ff000000</color>
+    <color name="appwidget_no_events">#bb000000</color>
+    <color name="appwidget_page_count">#ff666666</color>
 </resources>
 
diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml
index 8f38b96..032b91c 100644
--- a/res/values/donottranslate.xml
+++ b/res/values/donottranslate.xml
@@ -4,9 +4,9 @@
      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.
@@ -18,5 +18,5 @@
     <!-- Order of weekday/day label in week view -->
     <string name="weekday_day">%1$s %2$s</string>
     <!-- Order of month year label in month view -->
-    <string name="month_year">%B %Y</string>
+    <string name="month_year">%OB %Y</string>
 </resources>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index ac44790..d78fe37 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -4,9 +4,9 @@
      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.
@@ -15,5 +15,6 @@
 -->
 
 <resources>
-    <integer name="number_of_hours">10</integer>
+    <integer name="flip_interval">5000</integer>
+    <integer name="slide_transition_duration">600</integer>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 59dbbc7..8c2d149 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,6 +17,7 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- This is the label underneath the icon for Calendar -->
     <string name="app_label">Calendar</string>
+    <string name="app_aio_label">Calendar AllInOne</string>
 
     <!-- Shared Labels. These labels are shared among the activities. -->
     <skip />
@@ -24,8 +25,8 @@
     <string name="what_label">What</string>
     <!-- This is the label for the location of an event -->
     <string name="where_label">Where</string>
-    <!-- This is the label for the time zone of an event [CHAR LIMIT=30] -->
-    <string name="timezone_label">Time zone</string>
+    <!-- This is the label for the timezone of an event [CHAR LIMIT=30] -->
+    <string name="timezone_label">Timezone</string>
     <!-- This is the label for the Guests/Attendees of an event -->
     <string name="attendees_label">Guests</string>
     <!-- This is the label for the Guest Responses and count of an event e.g. Yes (3) -->
@@ -80,6 +81,9 @@
 
     <!-- Menu items: -->
     <skip />
+    <!-- This is a label on a context menu item. Pressing this menu item to initiate
+        syncing of your Calendar data with the server. [CHAR LIMIT=11] -->
+    <string name="calendar_refresh"> "Refresh"</string>
     <!-- This is a label on a context menu item. Pressing this menu item displays the
          Agenda view. -->
     <string name="show_agenda_view">"Show agenda"</string>
@@ -121,6 +125,9 @@
     <!-- This is a label on a menu item. Pressing this menu item allows the
          user to view and edit his Settings (or Preferences) -->
     <string name="menu_preferences">"Settings"</string>
+    <!-- This is a label on a menu item. Pressing this menu item allows the
+         user to search their events. [CHAR LIMIT=11]-->
+    <string name="search">"Search"</string>
 
     <!-- Month view -->
     <skip />
@@ -143,6 +150,18 @@
     <string name="synced_not_visible">synced, not visible</string>
     <!-- not Synced or visible status message -->
     <string name="not_synced_not_visible">not synced, not visible</string>
+    <!-- Synced status message displayed when the given calendar is stored/synced on the device
+        [CHAR LIMIT=60] -->
+    <string name="synced"> synced</string>
+    <!-- Not synced status message displayed when the given calendar is not stored/synced on the
+        device [CHAR LIMIT=60] -->
+    <string name="not_synced"> not synced</string>
+    <!-- Message displayed to user when an account with Calendars is not being auto-synced
+        [CHAR LIMIT=120]-->
+    <string name="acct_not_synced">This account is not being synced and your calendars may
+        not update correctly.</string>
+    <!-- Label on button that takes user to the Accounts and Sync settings page [CHAR LIMIT=60] -->
+    <string name="accounts">Accounts &amp; sync</string>
 
     <!-- Event edit activity -->
     <skip />
@@ -209,8 +228,8 @@
     <string name="edit_event_show_extra_options">Show extra options</string>
     <!-- Menu item to hide extra options that are used less often -->
     <string name="edit_event_hide_extra_options">Hide extra options</string>
-    <!-- Menu item to show all choices [CHAR LIMIT=24]-->
-    <string name="edit_event_show_all">Show all \u003E</string>
+    <!-- Menu item to show all choices [CHAR LIMIT=22]-->
+    <string name="edit_event_show_all">Show all</string>
     <!-- Label for the event description -->
     <string name="description_label">Description</string>
     <!-- Label for the 'Presence' of an event, which can be either 'busy' (the default)
@@ -223,20 +242,10 @@
     <string name="reminders_label">Reminders</string>
     <!-- Title of alert shown if the user tries to create an event and has no calendars -->
     <string name="no_syncable_calendars">No calendars</string>
-    <!-- Body of alert shown if the user tries to create an event and has no calendars -->
-    <string name="no_calendars_found">You have no calendars.</string>
-    <!-- The content description for the date part of the "From" input field. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_from_date">From date</string>
-    <!-- The content description for the date part of the "From" input field. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_from_time">From time</string>
-    <!-- The content description for the date part of the "To" input field. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_to_date">To date</string>
-    <!-- The content description for the date part of the "To" input field. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_to_time">To time</string>
-    <!-- The content description for the widget for selecting a reminder type. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_reminders">Reminders</string>
-    <!-- The content description for the widget for removing a reminder from an event. [CHAR LIMIT=NONE] -->
-    <string name="content_description_edit_event_remove_reminder">Remove reminder</string>
+    <!-- Body of alert shown if the user tries to create an event and has no calendars. Users can start adding an account here. [CHAR LIMIT=120] -->
+    <string name="no_calendars_found">Before you can add an event, you must add at least one Calendar account to the device. Touch \u0022Add account\u0022 to add an account. Or if you just added an account, wait for it to finish syncing and try again.</string>
+    <!-- Title of a button shown when there is no account available and the system prompt a user to add one. [CHAR LIMIT=none] -->
+    <string name="add_account">Add account</string>
 
     <!-- View Event -->
     <skip />
@@ -258,9 +267,14 @@
     <!-- This is shown while the calendar events are being loading to the screen. -->
     <string name="loading">Loading\u2026</string>
     <!-- This is shown at the top of the agenda view showing the range of events shown. -->
-    <string name="show_older_events">Showing events since <xliff:g id="oldest_search_range">%1$s</xliff:g>. Tap to look for more.</string>
+    <string name="show_older_events">Touch to view events before <xliff:g id="oldest_search_range">%1$s</xliff:g></string>
     <!-- This is shown at the bottom of the agenda view showing the range of events shown. -->
-    <string name="show_newer_events">Showing events until <xliff:g id="newest_search_range">%1$s</xliff:g>. Tap to look for more.</string>
+    <string name="show_newer_events">Touch to view events after <xliff:g id="newest_search_range">%1$s</xliff:g></string>
+
+    <!-- Search activity strings -->
+    <skip />
+    <!-- Title of the search screen [CHAR LIMIT=30] -->
+    <string name="search_title">Search my calendars</string>
 
     <!-- ICS Import activity -->
     <skip />
@@ -280,7 +294,7 @@
     <!-- The button label for saving an event -->
     <string name="save_label">Done</string>
     <!-- The button label for discarding changes to an event -->
-    <string name="discard_label">Revert</string>
+    <string name="discard_label">Cancel</string>
     <!-- The button label for importing events from another source to the phone -->
     <string name="import_label">"Import"</string>
 
@@ -356,6 +370,16 @@
          changing the response to an invitation. -->
     <string name="change_response_title">Change response</string>
 
+    <!-- This the title of a menu for General Preferences.
+         Pressing the menu item lets user to view and edit his general settings
+         (or preferences) [CHAR LIMIT=30] -->
+    <string name="menu_general_preferences">General Preferences</string>
+
+    <!-- This the title of a menu for "About Calendar" Preferences.
+         Pressing the menu item lets user to view the current settings
+         [CHAR LIMIT=30] -->
+    <string name="menu_about_preferences">About calendar</string>
+
     <!-- This is the title of the screen for setting user preferences. -->
     <string name="preferences_title">"Settings"</string>
     <!-- This is the title of a section in the Settings screen for settings
@@ -363,23 +387,31 @@
     <string name="preferences_general_title">Calendar view setting</string>
     <!-- This is the title of a section in the Settings screen for settings
          related to reminders for calendar events -->
-    <string name="preferences_alerts_title">Reminder settings</string>
+    <string name="preferences_reminder_title">Reminder settings</string>
     <!-- Settings check box label to hide events that the user has declined to attend -->
     <string name="preferences_hide_declined_title">Hide declined events</string>
-    <!-- Settings option to bring up a dialog that lets the user choose how
-         reminder notifications should occur. -->
-    <string name="preferences_alerts_type_title">Set alerts &amp; notifications</string>
-    <!-- The title of a popup dialog that lets the user choose how
-         reminder notifications should occur. -->
-    <string name="preferences_alerts_type_dialog">Alerts &amp; notifications</string>
+    <!-- Settings week start label to start week on specific day-->
+    <string name="preferences_week_start_day_title">Week starts on</string>
+    <!-- Title of popup for week start day setting-->
+    <string name="preferences_week_start_day_dialog">Week starts on</string>
     <!-- DO NOT TRANSLATE -->
-    <string name="preferences_alerts_type_default">1</string>
+    <string name="preferences_week_start_day_default">-1</string>
+    <!-- Title of the settings item to clear the recent search history [CHAR LIMIT=40] -->
+    <string name="preferences_clear_search_history_title">Clear search history</string>
+    <!-- Summary of the settings item to clear the recent search history [CHAR LIMIT=70]-->
+    <string name="preferences_clear_search_history_summary">Remove all the searches you have performed</string>
+    <!-- Message to show in a toast when the history is cleared [CHAR LIMIT=40] -->
+    <string name="search_history_cleared">Search history cleared</string>
+    <!-- Settings check box label to enable or disable notifications -->
+    <string name="preferences_alerts_title">Notifications</string>
     <!-- Settings dialog label that specifies when the phone should vibrate -->
     <string name="preferences_alerts_vibrateWhen_title">Vibrate</string>
     <!-- Settings screen, setting summary text -->
     <string name="preferences_alerts_vibrateWhen_summary">Also vibrate for reminders</string>
     <!-- Title of ringtone selector dialog -->
     <string name="preferences_alerts_ringtone_title">Select ringtone</string>
+    <!-- Title of check box label to enable or disable pop-up notifications -->
+    <string name="preferences_alerts_popup_title">Pop-up notification</string>
     <!-- List item label for setting the default number of reminder minutes -->
     <string name="preferences_default_reminder_title">Default reminder time</string>
     <!-- Title of dialog for setting the default number of reminder minutes -->
@@ -428,4 +460,33 @@
     <!-- Dialog title for the Vibrate dialog -->
     <string name="prefDialogTitle_vibrateWhen">Vibrate</string>
 
+    <!-- Widget -->
+    <skip />
+    <!-- Title for calendar gadget when displayed in list of all other gadgets -->
+    <string name="gadget_title">Calendar</string>
+
+    <!-- Shown in gadget when additional events are available for display, but no room remaining -->
+    <plurals name="gadget_more_events">
+        <!-- additional events message for 1 event -->
+        <item quantity="one">1 more event</item>
+        <!-- additional events message for multiple events -->
+        <item quantity="other"><xliff:g id="number">%d</xliff:g> more events</item>
+    </plurals>
+
+    <!-- Caption to show on gadget when there are no upcoming calendar events -->
+    <string name="gadget_no_events">No upcoming calendar events</string>
+
+    <!-- Text to show on gadget when an event starts on the next day -->
+    <string name="tomorrow">Tomorrow</string>
+
+    <!-- Text to show on gadget when an event is currently in progress -->
+    <string name="in_progress">in progress</string>
+
+    <!-- Text to show on gadget when an all-day event is in progress -->
+    <string name="today">Today</string>
+
+    <!-- Displayed in email address autocomplete list when searching for contacts
+         in corporate directories. Example:  "Searching bigcompany.com..." [CHAR LIMIT=64] -->
+    <string name="directory_searching_fmt">Searching <xliff:g id="domain">%s</xliff:g>\u2026</string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 62659f8..797a22d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -54,30 +54,21 @@
 
     <style name="TextAppearance.AgendaView_ValueLabel">
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@android:color/black</item>
     </style>
 
     <style name="TextAppearance.EditEvent_Label">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
-        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
         <item name="android:textStyle">bold</item>
         <item name="android:paddingLeft">2dip</item>
     </style>
 
     <style name="TextAppearance.EventInfo_Label">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textStyle">bold</item>
         <item name="android:paddingLeft">2dip</item>
     </style>
 
-    <style name="TextAppearance.EditEvent_homeTime">
-        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
-        <item name="android:textColor">@android:color/darker_gray</item>
-        <item name="android:paddingRight">2dip</item>
-    </style>
-
-    <style name="EditEvent_Layout">
+    <style name="EditEvent_Layout" parent="android:Theme.Light.Holo">
         <item name="android:paddingLeft">6dip</item>
         <item name="android:paddingRight">7dip</item>
         <item name="android:paddingTop">8dip</item>
@@ -85,30 +76,98 @@
 
     <style name="TextAppearance.Alert_Title">
         <item name="android:textSize">18sp</item>
-        <item name="android:textColor">@android:color/white</item>
     </style>
 
     <style name="TextAppearance.Alert_Label">
         <item name="android:textSize">14sp</item>
         <item name="android:textStyle">bold</item>
-        <item name="android:textColor">@android:color/white</item>
     </style>
 
     <style name="TextAppearance.Alert_Value">
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@android:color/white</item>
     </style>
 
-    <style name="CalendarTheme" parent="android:Theme.Light.NoTitleBar">
+    <style name="CalendarTheme" parent="android:Theme.Light.Holo">
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
+    <style name="CalendarTheme.WithActionBar">
+        <item name="android:windowNoTitle">false</item>
+        <item name="android:windowActionBar">true</item>
+    </style>
+
     <style name="MultiStateButton">
         <item name="android:focusable">true</item>
         <item name="android:clickable">true</item>
-        <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
-        <item name="android:textColor">@android:color/primary_text_light</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
         <item name="android:gravity">center_vertical|left</item>
     </style>
 
+
+    <style name="TextAppearance.WidgetTitle">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">7dip</item>
+        <item name="android:layout_marginRight">7dip</item>
+        <item name="android:layout_marginTop">3dip</item>
+        <item name="android:layout_marginBottom">-3dip</item>
+
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@color/appwidget_title</item>
+        <item name="android:maxLines">2</item>
+    </style>
+
+    <style name="TextAppearance.WidgetWhen">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">7dip</item>
+        <item name="android:layout_marginRight">7dip</item>
+
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/appwidget_when</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
+    <style name="TextAppearance.WidgetWhere">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">7dip</item>
+        <item name="android:layout_marginRight">7dip</item>
+
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/appwidget_where</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
+
+
+    <style name="TextAppearance.WidgetConflict">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingLeft">7dip</item>
+        <item name="android:paddingRight">7dip</item>
+
+        <item name="android:layout_marginBottom">4dip</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">@color/appwidget_conflict</item>
+    </style>
+
+    <style name="TextAppearance.WidgetPageCount">
+        <item name="android:visibility">gone</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">5dip</item>
+        <item name="android:layout_marginRight">5dip</item>
+        <item name="android:layout_marginBottom">5dip</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/appwidget_when</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
 </resources>
diff --git a/res/values-land/integers.xml b/res/xml/about_preferences.xml
similarity index 60%
copy from res/values-land/integers.xml
copy to res/xml/about_preferences.xml
index 9ece108..e9c11c8 100644
--- a/res/values-land/integers.xml
+++ b/res/xml/about_preferences.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2010 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.
@@ -14,6 +14,13 @@
      limitations under the License.
 -->
 
-<resources>
-    <integer name="number_of_hours">8</integer>
-</resources>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceCategory
+        android:title="@string/preferences_about_title">
+        <Preference
+            android:key="build_version"
+            android:title="@string/preferences_build_version" />
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/values-land/integers.xml b/res/xml/appwidget_info.xml
similarity index 66%
copy from res/values-land/integers.xml
copy to res/xml/appwidget_info.xml
index 9ece108..2c5a159 100644
--- a/res/values-land/integers.xml
+++ b/res/xml/appwidget_info.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2009 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.
@@ -14,6 +14,10 @@
      limitations under the License.
 -->
 
-<resources>
-    <integer name="number_of_hours">8</integer>
-</resources>
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="292dip"
+    android:minHeight="292dip"
+    android:updatePeriodMillis="0"
+    android:initialLayout="@layout/appwidget"
+    >
+</appwidget-provider>
diff --git a/res/xml/calendar_settings_headers.xml b/res/xml/calendar_settings_headers.xml
new file mode 100644
index 0000000..a5a5ae7
--- /dev/null
+++ b/res/xml/calendar_settings_headers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<preference-headers
+        xmlns:android="http://schemas.android.com/apk/res/android">
+    <header android:fragment="com.android.calendar.GeneralPreferences"
+            android:title="@string/menu_general_preferences" />
+
+    <header android:fragment="com.android.calendar.AboutPreferences"
+            android:title="@string/menu_about_preferences" />
+</preference-headers>
diff --git a/res/xml/preferences.xml b/res/xml/general_preferences.xml
similarity index 73%
rename from res/xml/preferences.xml
rename to res/xml/general_preferences.xml
index e7dff06..2e0bf14 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/general_preferences.xml
@@ -20,13 +20,22 @@
             android:key="preferences_hide_declined"
             android:defaultValue="false"
             android:title="@string/preferences_hide_declined_title" />
-
+        <ListPreference
+            android:key="preferences_week_start_day"
+            android:defaultValue="@string/preferences_week_start_day_default"
+            android:title="@string/preferences_week_start_day_title"
+            android:entries="@array/preferences_week_start_day_labels"
+            android:entryValues="@array/preferences_week_start_day_values"
+            android:dialogTitle="@string/preferences_week_start_day_dialog" />
+        <PreferenceScreen
+            android:key="preferences_clear_search_history"
+            android:title="@string/preferences_clear_search_history_title"
+            android:summary="@string/preferences_clear_search_history_summary" />
         <CheckBoxPreference
             android:key="preferences_home_tz_enabled"
             android:defaultValue="false"
             android:title="@string/preferences_use_home_tz_title"
             android:summary="@string/preferences_use_home_tz_descrip" />
-
         <ListPreference
             android:key="preferences_home_tz"
             android:dependency="preferences_home_tz_enabled"
@@ -37,14 +46,11 @@
             android:dialogTitle="@string/preferences_home_tz_title" />
     </PreferenceCategory>
 
-    <PreferenceCategory android:title="@string/preferences_alerts_title">
-        <ListPreference
-            android:key="preferences_alerts_type"
-            android:defaultValue="@string/preferences_alerts_type_default"
-            android:title="@string/preferences_alerts_type_title"
-            android:entries="@array/preferences_alert_type_labels"
-            android:entryValues="@array/preferences_alert_type_values"
-            android:dialogTitle="@string/preferences_alerts_type_dialog" />
+    <PreferenceCategory android:title="@string/preferences_reminder_title">
+        <CheckBoxPreference
+            android:key="preferences_alerts"
+            android:defaultValue="true"
+            android:title="@string/preferences_alerts_title" />
 
         <RingtonePreference
             android:layout="?android:attr/preferenceLayoutChild"
@@ -63,6 +69,12 @@
             android:defaultValue="@string/prefDefault_alerts_vibrateWhen"
             android:dialogTitle="@string/prefDialogTitle_vibrateWhen" />
 
+        <CheckBoxPreference
+            android:layout="?android:attr/preferenceLayoutChild"
+            android:key="preferences_alerts_popup"
+            android:defaultValue="false"
+            android:title="@string/preferences_alerts_popup_title" />
+
         <ListPreference
             android:key="preferences_default_reminder"
             android:defaultValue="@string/preferences_default_reminder_default"
@@ -72,11 +84,4 @@
             android:dialogTitle="@string/preferences_default_reminder_dialog" />
     </PreferenceCategory>
 
-    <PreferenceCategory
-        android:title="@string/preferences_about_title">
-        <Preference
-            android:key="build_version"
-            android:title="@string/preferences_build_version" />
-    </PreferenceCategory>
-
 </PreferenceScreen>
diff --git a/res/values-land/integers.xml b/res/xml/searchable.xml
similarity index 65%
copy from res/values-land/integers.xml
copy to res/xml/searchable.xml
index 9ece108..814dae1 100644
--- a/res/values-land/integers.xml
+++ b/res/xml/searchable.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2010 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.
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 
-<resources>
-    <integer name="number_of_hours">8</integer>
-</resources>
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label="@string/app_label"
+    android:searchSuggestAuthority="com.android.calendar.CalendarRecentSuggestionsProvider"
+    android:searchSuggestSelection=" ?" >
+</searchable>
\ No newline at end of file
diff --git a/src/com/android/calendar/AboutPreferences.java b/src/com/android/calendar/AboutPreferences.java
new file mode 100644
index 0000000..37dd5e3
--- /dev/null
+++ b/src/com/android/calendar/AboutPreferences.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+public class AboutPreferences extends PreferenceFragment {
+    private static final String BUILD_VERSION = "build_version";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        addPreferencesFromResource(R.xml.about_preferences);
+
+        final Activity activity = getActivity();
+        try {
+            final PackageInfo packageInfo =
+                activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0);
+            findPreference(BUILD_VERSION).setSummary(packageInfo.versionName);
+        } catch (NameNotFoundException e) {
+            findPreference(BUILD_VERSION).setSummary("?");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/AbstractCalendarActivity.java b/src/com/android/calendar/AbstractCalendarActivity.java
new file mode 100644
index 0000000..079ad95
--- /dev/null
+++ b/src/com/android/calendar/AbstractCalendarActivity.java
@@ -0,0 +1,14 @@
+package com.android.calendar;
+
+import android.app.Activity;
+
+public abstract class AbstractCalendarActivity extends Activity {
+    protected AsyncQueryService mService;
+
+    public synchronized AsyncQueryService getAsyncQueryService() {
+        if (mService == null) {
+            mService = new AsyncQueryService(this);
+        }
+        return mService;
+    }
+}
diff --git a/src/com/android/calendar/AgendaActivity.java b/src/com/android/calendar/AgendaActivity.java
deleted file mode 100644
index f6ee7ca..0000000
--- a/src/com/android/calendar/AgendaActivity.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import dalvik.system.VMRuntime;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Calendar.Events;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import java.util.Locale;
-import java.util.TimeZone;
-
-public class AgendaActivity extends Activity implements Navigator {
-
-    private static final String TAG = "AgendaActivity";
-
-    private static boolean DEBUG = false;
-
-    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
-
-    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
-
-    private ContentResolver mContentResolver;
-
-    private AgendaListView mAgendaListView;
-
-    private Time mTime;
-
-    private String mTitle;
-
-    // This gets run if the time zone is updated in the db
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            long time = mTime.toMillis(true);
-            mTime = new Time(Utils.getTimeZone(AgendaActivity.this, this));
-            mTime.set(time);
-            updateTitle();
-        }
-    };
-
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_TIME_CHANGED)
-                    || action.equals(Intent.ACTION_DATE_CHANGED)
-                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
-                mAgendaListView.refresh(true);
-            }
-        }
-    };
-
-    private ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return true;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            mAgendaListView.refresh(true);
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
-        // TODO: We should restore the old heap size once the activity reaches the idle state
-        VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
-
-        mAgendaListView = new AgendaListView(this);
-        setContentView(mAgendaListView);
-
-        mContentResolver = getContentResolver();
-
-        mTitle = getResources().getString(R.string.agenda_view);
-
-        long millis = 0;
-        mTime = new Time(Utils.getTimeZone(this, mUpdateTZ));
-        if (icicle != null) {
-            // Returns 0 if key not found
-            millis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME);
-            if (DEBUG) {
-                Log.v(TAG, "Restore value from icicle: " + millis);
-            }
-        }
-
-        if (millis == 0) {
-            // Returns 0 if key not found
-            millis = getIntent().getLongExtra(EVENT_BEGIN_TIME, 0);
-            if (DEBUG) {
-                Time time = new Time();
-                time.set(millis);
-                Log.v(TAG, "Restore value from intent: " + time.toString());
-            }
-        }
-
-        if (millis == 0) {
-            if (DEBUG) {
-                Log.v(TAG, "Restored from current time");
-            }
-            millis = System.currentTimeMillis();
-        }
-        mTime.set(millis);
-        updateTitle();
-    }
-
-    private void updateTitle() {
-        StringBuilder title = new StringBuilder(mTitle);
-        String tz = Utils.getTimeZone(this, mUpdateTZ);
-        if (!TextUtils.equals(tz, Time.getCurrentTimezone())) {
-            int flags = DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(this)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            boolean isDST = mTime.isDst != 0;
-            long start = System.currentTimeMillis();
-            TimeZone timeZone = TimeZone.getTimeZone(tz);
-            title.append(" (").append(Utils.formatDateRange(this, start, start, flags)).append(" ")
-                    .append(timeZone.getDisplayName(isDST, TimeZone.SHORT, Locale.getDefault()))
-                    .append(")");
-        }
-        setTitle(title.toString());
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        long time = Utils.timeFromIntentInMillis(intent);
-        if (time > 0) {
-            mTime.set(time);
-            goTo(mTime, false);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (DEBUG) {
-            Log.v(TAG, "OnResume to " + mTime.toString());
-        }
-
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(
-                getApplicationContext());
-        boolean hideDeclined = prefs.getBoolean(
-                CalendarPreferenceActivity.KEY_HIDE_DECLINED, false);
-
-        mAgendaListView.setHideDeclinedEvents(hideDeclined);
-        mAgendaListView.goTo(mTime, true);
-        mAgendaListView.onResume();
-
-        // Register for Intent broadcasts
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_DATE_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        registerReceiver(mIntentReceiver, filter);
-
-        mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
-        mUpdateTZ.run();
-
-        // Record Agenda View as the (new) default detailed view.
-        Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID);
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        long firstVisibleTime = mAgendaListView.getFirstVisibleTime();
-        if (firstVisibleTime > 0) {
-            mTime.set(firstVisibleTime);
-            outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime);
-            if (DEBUG) {
-                Log.v(TAG, "onSaveInstanceState " + mTime.toString());
-            }
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        mAgendaListView.onPause();
-        mContentResolver.unregisterContentObserver(mObserver);
-        unregisterReceiver(mIntentReceiver);
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MenuHelper.onPrepareOptionsMenu(this, menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuHelper.onCreateOptionsMenu(menu);
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        MenuHelper.onOptionsItemSelected(this, item, this);
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DEL:
-                // Delete the currently selected event (if any)
-                mAgendaListView.deleteSelectedEvent();
-                break;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    /* Navigator interface methods */
-    public void goToToday() {
-        Time now = new Time(Utils.getTimeZone(this, mUpdateTZ));
-        now.setToNow();
-        mAgendaListView.goTo(now, true); // Force refresh
-    }
-
-    public void goTo(Time time, boolean animate) {
-        mAgendaListView.goTo(time, false);
-    }
-
-    public long getSelectedTime() {
-        return mAgendaListView.getSelectedTime();
-    }
-
-    public boolean getAllDay() {
-        return false;
-    }
-}
-
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
new file mode 100644
index 0000000..da9eecb
--- /dev/null
+++ b/src/com/android/calendar/AllInOneActivity.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+import static android.provider.Calendar.EVENT_END_TIME;
+
+import com.android.calendar.CalendarController.EventHandler;
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.CalendarController.ViewType;
+import com.android.calendar.agenda.AgendaFragment;
+import com.android.calendar.event.EditEventFragment;
+import com.android.calendar.selectcalendars.SelectCalendarsFragment;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Calendar;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class AllInOneActivity extends Activity implements EventHandler,
+        OnSharedPreferenceChangeListener, SearchView.OnQueryChangeListener {
+    private static final String TAG = "AllInOneActivity";
+    private static final boolean DEBUG = false;
+    private static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+    private static final String BUNDLE_KEY_RESTORE_EDIT = "key_restore_edit";
+    private static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
+    private static final int HANDLER_KEY = 0;
+    private static CalendarController mController;
+    private static boolean mIsMultipane;
+    private ContentResolver mContentResolver;
+    private int mPreviousView;
+    private int mCurrentView;
+    private boolean mPaused = true;
+    private boolean mUpdateOnResume = false;
+    private TextView mHomeTime;
+
+    private Runnable mHomeTimeUpdater = new Runnable() {
+        @Override
+        public void run() {
+            updateHomeClock();
+        }
+    };
+
+    // Create an observer so that we can update the views whenever a
+    // Calendar event changes.
+    private ContentObserver mObserver = new ContentObserver(new Handler()) {
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            eventsChanged();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // This needs to be created before setContentView
+        mController = CalendarController.getInstance(this);
+        // Get time from intent or icicle
+        long timeMillis;
+        if (icicle != null) {
+            timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME);
+        } else {
+            timeMillis = Utils.timeFromIntentInMillis(getIntent());
+        }
+        boolean restoreEdit = icicle != null ? icicle.getBoolean(BUNDLE_KEY_RESTORE_EDIT, false)
+                : false;
+        int viewType;
+        if (restoreEdit) {
+            viewType = ViewType.EDIT;
+        } else {
+            viewType = Utils.getViewTypeFromIntentAndSharedPref(this);
+        }
+        Time t = new Time();
+        t.set(timeMillis);
+
+        if (icicle != null && getIntent() != null) {
+            Log.d(TAG, "both, icicle:" + icicle.toString() + "  intent:" + getIntent().toString());
+        } else {
+            Log.d(TAG, "not both, icicle:" + icicle + " intent:" + getIntent());
+        }
+
+        mIsMultipane = (getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_XLARGE) != 0;
+
+        // Must be the first to register because this activity can modify the
+        // list of event handlers in it's handle method. This affects who the
+        // rest of the handlers the controller dispatches to are.
+        mController.registerEventHandler(HANDLER_KEY, this);
+
+        setContentView(R.layout.all_in_one);
+        mHomeTime = (TextView) findViewById(R.id.home_time);
+
+        initFragments(timeMillis, viewType, icicle);
+
+
+        // Listen for changes that would require this to be refreshed
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
+        prefs.registerOnSharedPreferenceChangeListener(this);
+
+        mContentResolver = getContentResolver();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mContentResolver.registerContentObserver(Calendar.Events.CONTENT_URI, true, mObserver);
+        if (mUpdateOnResume) {
+            initFragments(mController.getTime(), mController.getViewType(), null);
+            mUpdateOnResume = false;
+        }
+        updateHomeClock();
+        mPaused = false;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mPaused = true;
+        mHomeTime.getHandler().removeCallbacks(mHomeTimeUpdater);
+        mContentResolver.unregisterContentObserver(mObserver);
+        if (isFinishing()) {
+            // Stop listening for changes that would require this to be refreshed
+            SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
+            prefs.unregisterOnSharedPreferenceChangeListener(this);
+        }
+        // FRAG_TODO save highlighted days of the week;
+        if (mController.getViewType() != ViewType.EDIT) {
+            Utils.setDefaultView(this, mController.getViewType());
+        }
+    }
+
+    @Override
+    protected void onUserLeaveHint() {
+        mController.sendEvent(this, EventType.USER_HOME, null, null, -1, ViewType.CURRENT);
+        super.onUserLeaveHint();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime());
+        if (mCurrentView == ViewType.EDIT) {
+            outState.putBoolean(BUNDLE_KEY_RESTORE_EDIT, true);
+            outState.putLong(BUNDLE_KEY_EVENT_ID, mController.getEventId());
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(this);
+        prefs.unregisterOnSharedPreferenceChangeListener(this);
+        CalendarController.removeInstance(this);
+    }
+
+    private void initFragments(long timeMillis, int viewType, Bundle icicle) {
+        FragmentTransaction ft = getFragmentManager().openTransaction();
+
+        if (mIsMultipane) {
+            Fragment miniMonthFrag = new MonthFragment(false, timeMillis, true);
+            ft.replace(R.id.mini_month, miniMonthFrag);
+            mController.registerEventHandler(R.id.mini_month, (EventHandler) miniMonthFrag);
+
+            Fragment selectCalendarsFrag = new SelectCalendarsFragment();
+            ft.replace(R.id.calendar_list, selectCalendarsFrag);
+        }
+        if (!mIsMultipane || viewType == ViewType.EDIT) {
+            findViewById(R.id.mini_month).setVisibility(View.GONE);
+            findViewById(R.id.calendar_list).setVisibility(View.GONE);
+        }
+
+        EventInfo info = null;
+        if (viewType == ViewType.EDIT) {
+            mPreviousView = GeneralPreferences.getSharedPreferences(this).getInt(
+                    GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
+
+            long eventId = -1;
+            Intent intent = getIntent();
+            Uri data = intent.getData();
+            if (data != null) {
+                try {
+                    eventId = Long.parseLong(data.getLastPathSegment());
+                } catch (NumberFormatException e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Create new event");
+                    }
+                }
+            } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) {
+                eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID);
+            }
+
+            long begin = intent.getLongExtra(EVENT_BEGIN_TIME, -1);
+            long end = intent.getLongExtra(EVENT_END_TIME, -1);
+            info = new EventInfo();
+            if (end != -1) {
+                info.endTime = new Time();
+                info.endTime.set(end);
+            }
+            if (begin != -1) {
+                info.startTime = new Time();
+                info.startTime.set(begin);
+            }
+            info.id = eventId;
+            // We set the viewtype so if the user presses back when they are
+            // done editing the controller knows we were in the Edit Event
+            // screen. Likewise for eventId
+            mController.setViewType(viewType);
+            mController.setEventId(eventId);
+        } else {
+            mPreviousView = viewType;
+        }
+        setMainPane(ft, R.id.main_pane, viewType, timeMillis, true, info);
+
+        ft.commit(); // this needs to be after setMainPane()
+
+        Time t = new Time();
+        t.set(timeMillis);
+        if (viewType != ViewType.EDIT) {
+            mController.sendEvent(this, EventType.GO_TO, t, null, -1, viewType);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mPreviousView == mCurrentView) {
+            super.onBackPressed();
+        } else {
+            mController.sendEvent(this, EventType.GO_TO, null, null, -1, mPreviousView);
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu);
+
+        SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+        searchView.setIconifiedByDefault(true);
+        searchView.setOnQueryChangeListener(this);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Time t = null;
+        int viewType = ViewType.CURRENT;
+        switch (item.getItemId()) {
+            case R.id.action_refresh:
+                mController.refreshCalendars();
+                return true;
+            case R.id.action_day:
+                viewType = ViewType.DAY;
+                break;
+            case R.id.action_week:
+                viewType = ViewType.WEEK;
+                break;
+            case R.id.action_month:
+                viewType = ViewType.MONTH;
+                break;
+            case R.id.action_today:
+                viewType = ViewType.CURRENT;
+                t = new Time();
+                t.setToNow();
+                break;
+            case R.id.action_create_event:
+                mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1, 0, 0, 0, 0);
+                return true;
+            case R.id.action_settings:
+                mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0);
+                return true;
+            default:
+                return false;
+        }
+        mController.sendEvent(this, EventType.GO_TO, t, null, -1, viewType);
+        return true;
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) {
+            if (mPaused) {
+                mUpdateOnResume = true;
+            } else {
+                initFragments(mController.getTime(), mController.getViewType(), null);
+            }
+        }
+    }
+
+    private void setMainPane(FragmentTransaction ft, int viewId, int viewType, long timeMillis,
+            boolean force, EventInfo e) {
+        if (!force && mCurrentView == viewType) {
+            return;
+        }
+
+        if (viewType != mCurrentView) {
+            // The rules for this previous view are different than the
+            // controller's and are used for intercepting the back button.
+            if (mCurrentView != ViewType.EDIT && mCurrentView > 0) {
+                mPreviousView = mCurrentView;
+            }
+            mCurrentView = viewType;
+        }
+        // Create new fragment
+        Fragment frag;
+        switch (viewType) {
+            case ViewType.AGENDA:
+                frag = new AgendaFragment(timeMillis);
+                break;
+            case ViewType.DAY:
+                frag = new DayFragment(timeMillis, 1);
+                break;
+            case ViewType.WEEK:
+                frag = new DayFragment(timeMillis, 7);
+                break;
+            case ViewType.MONTH:
+                frag = new MonthFragment(false, timeMillis, false);
+                break;
+            case ViewType.EDIT:
+                frag = new EditEventFragment(e, mPreviousView);
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Must be Agenda, Day, Week, or Month ViewType, not " + viewType);
+        }
+
+        boolean doCommit = false;
+        if (ft == null) {
+            doCommit = true;
+            ft = getFragmentManager().openTransaction();
+        }
+
+        ft.replace(viewId, frag);
+        if (DEBUG) {
+            Log.d(TAG, "Adding handler with viewId " + viewId + " and type " + viewType);
+        }
+        // If the key is already registered this will replace it
+        mController.registerEventHandler(viewId, (EventHandler) frag);
+
+        if (doCommit) {
+            ft.commit();
+        }
+    }
+
+    private void setTitleInActionBar(EventInfo event) {
+        if (event.eventType != EventType.GO_TO) {
+            return;
+        }
+
+        long start = event.startTime.toMillis(false /* use isDst */);
+        long end = start;
+
+        if (event.endTime != null) {
+            end = event.endTime.toMillis(false /* use isDst */);
+        }
+        String msg = DateUtils.formatDateRange(this, start, end, DateUtils.FORMAT_SHOW_DATE);
+
+        ActionBar ab = getActionBar();
+        if (ab != null) {
+            ab.setTitle(msg);
+        }
+    }
+
+    private void updateHomeClock() {
+        String tz = Utils.getTimeZone(this, mHomeTimeUpdater);
+        if (mIsMultipane && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK)
+                && !TextUtils.equals(tz, Time.getCurrentTimezone())) {
+            Time time = new Time(tz);
+            time.setToNow();
+            long millis = time.toMillis(true);
+            boolean isDST = time.isDst != 0;
+            int flags = DateUtils.FORMAT_SHOW_TIME;
+            if (DateFormat.is24HourFormat(this)) {
+                flags |= DateUtils.FORMAT_24HOUR;
+            }
+            // Formats the time as
+            String timeString =
+                    (new StringBuilder(Utils.formatDateRange(this, millis, millis, flags)))
+                    .append(" ").append(TimeZone.getTimeZone(tz).getDisplayName(
+                            isDST, TimeZone.SHORT, Locale.getDefault()))
+                    .toString();
+            mHomeTime.setText(timeString);
+            mHomeTime.setVisibility(View.VISIBLE);
+            // Update when the minute changes
+            mHomeTime.postDelayed(
+                    mHomeTimeUpdater, DateUtils.MINUTE_IN_MILLIS - (millis % DateUtils.MINUTE_IN_MILLIS));
+        } else {
+            mHomeTime.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.GO_TO | EventType.VIEW_EVENT | EventType.EDIT_EVENT
+                | EventType.CREATE_EVENT;
+    }
+
+    @Override
+    public void handleEvent(EventInfo event) {
+        if (event.eventType == EventType.GO_TO) {
+            if (mCurrentView == ViewType.EDIT) {
+                // If we are leaving the edit view ping it so it has a chance to
+                // save if it needs to
+                EventHandler editHandler = (EventHandler) getFragmentManager().findFragmentById(
+                        R.id.main_pane);
+                editHandler.handleEvent(event);
+            }
+            // Set title bar
+            setTitleInActionBar(event);
+
+            setMainPane(null, R.id.main_pane, event.viewType, event.startTime.toMillis(false),
+                    false, event);
+
+            if (!mIsMultipane) {
+                return;
+            }
+            if (event.viewType == ViewType.MONTH) {
+                // hide minimonth and calendar frag
+                findViewById(R.id.mini_month).setVisibility(View.GONE);
+                findViewById(R.id.calendar_list).setVisibility(View.GONE);
+            } else {
+                // show minimonth and calendar frag
+                findViewById(R.id.mini_month).setVisibility(View.VISIBLE);
+                findViewById(R.id.calendar_list).setVisibility(View.VISIBLE);
+            }
+        } else if (event.eventType == EventType.VIEW_EVENT) {
+            EventInfoFragment fragment = new EventInfoFragment(
+                    event.id, event.startTime.toMillis(false), event.endTime.toMillis(false));
+            fragment.setDialogParams(event.x, event.y);
+            fragment.show(getFragmentManager(), "EventInfoFragment");
+        } else if (event.eventType == EventType.EDIT_EVENT
+                || event.eventType == EventType.CREATE_EVENT) {
+            setMainPane(null, R.id.main_pane, ViewType.EDIT, -1, true, event);
+            // hide minimonth and calendar frag
+            findViewById(R.id.mini_month).setVisibility(View.GONE);
+            findViewById(R.id.calendar_list).setVisibility(View.GONE);
+        }
+        updateHomeClock();
+    }
+
+    @Override
+    public void eventsChanged() {
+        mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
+    }
+
+    @Override
+    public boolean getAllDay() {
+        return false;
+    }
+
+    @Override
+    public long getSelectedTime() {
+        return 0;
+    }
+
+    @Override
+    public void goTo(Time time, boolean animate) {
+    }
+
+    @Override
+    public void goToToday() {
+    }
+
+    @Override
+    public boolean onQueryTextChanged(String newText) {
+        return false;
+    }
+
+    @Override
+    public boolean onSubmitQuery(String query) {
+        mController.sendEvent(this, EventType.SEARCH, null, null, -1, ViewType.CURRENT, query,
+                getComponentName());
+        return false;
+    }
+}
diff --git a/src/com/android/calendar/AsyncQueryService.java b/src/com/android/calendar/AsyncQueryService.java
new file mode 100644
index 0000000..1bdf1bc
--- /dev/null
+++ b/src/com/android/calendar/AsyncQueryService.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import com.android.calendar.AsyncQueryServiceHelper.OperationInfo;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A helper class that executes {@link ContentResolver} calls in a background
+ * {@link android.app.Service}. This minimizes the chance of the call getting
+ * lost because the caller ({@link android.app.Activity}) is killed. It is
+ * designed for easy migration from {@link android.content.AsyncQueryHandler}
+ * which calls the {@link ContentResolver} in a background thread. This supports
+ * query/insert/update/delete and also batch mode i.e.
+ * {@link ContentProviderOperation}. It also supports delay execution and cancel
+ * which allows for time-limited undo. Note that there's one queue per
+ * application which serializes all the calls.
+ */
+public class AsyncQueryService extends Handler {
+    private static final String TAG = "AsyncQuery";
+    static final boolean localLOGV = true;
+
+    // Used for generating unique tokens for calls to this service
+    private static AtomicInteger mUniqueToken = new AtomicInteger(0);
+
+    private Context mContext;
+    private Handler mHandler = this; // can be overridden for testing
+
+    /**
+     * Data class which holds into info of the queued operation
+     */
+    public static class Operation {
+        static final int EVENT_ARG_QUERY = 1;
+        static final int EVENT_ARG_INSERT = 2;
+        static final int EVENT_ARG_UPDATE = 3;
+        static final int EVENT_ARG_DELETE = 4;
+        static final int EVENT_ARG_BATCH = 5;
+
+        /**
+         * unique identify for cancellation purpose
+         */
+        public int token;
+
+        /**
+         * One of the EVENT_ARG_ constants in the class describing the operation
+         */
+        public int op;
+
+        /**
+         * {@link SystemClock.elapsedRealtime()} based
+         */
+        public long scheduledExecutionTime;
+
+        protected static char opToChar(int op) {
+            switch (op) {
+                case Operation.EVENT_ARG_QUERY:
+                    return 'Q';
+                case Operation.EVENT_ARG_INSERT:
+                    return 'I';
+                case Operation.EVENT_ARG_UPDATE:
+                    return 'U';
+                case Operation.EVENT_ARG_DELETE:
+                    return 'D';
+                case Operation.EVENT_ARG_BATCH:
+                    return 'B';
+                default:
+                    return '?';
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Operation [op=");
+            builder.append(op);
+            builder.append(", token=");
+            builder.append(token);
+            builder.append(", scheduledExecutionTime=");
+            builder.append(scheduledExecutionTime);
+            builder.append("]");
+            return builder.toString();
+        }
+    }
+
+    public AsyncQueryService(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * returns a practically unique token for db operations
+     */
+    public final int getNextToken() {
+        return mUniqueToken.getAndIncrement();
+    }
+
+    /**
+     * Gets the last delayed operation. It is typically used for canceling.
+     *
+     * @return Operation object which contains of the last cancelable operation
+     */
+    public final Operation getLastCancelableOperation() {
+        return AsyncQueryServiceHelper.getLastCancelableOperation();
+    }
+
+    /**
+     * Attempts to cancel operation that has not already started. Note that
+     * there is no guarantee that the operation will be canceled. They still may
+     * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after
+     * this call has completed.
+     *
+     * @param token The token representing the operation to be canceled. If
+     *            multiple operations have the same token they will all be
+     *            canceled.
+     */
+    public final int cancelOperation(int token) {
+        return AsyncQueryServiceHelper.cancelOperation(token);
+    }
+
+    /**
+     * This method begins an asynchronous query. When the query is done
+     * {@link #onQueryComplete} is called.
+     *
+     * @param token A token passed into {@link #onQueryComplete} to identify the
+     *            query.
+     * @param cookie An object that gets passed into {@link #onQueryComplete}
+     * @param uri The URI, using the content:// scheme, for the content to
+     *            retrieve.
+     * @param projection A list of which columns to return. Passing null will
+     *            return all columns, which is discouraged to prevent reading
+     *            data from storage that isn't going to be used.
+     * @param selection A filter declaring which rows to return, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will return all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *            replaced by the values from selectionArgs, in the order that
+     *            they appear in the selection. The values will be bound as
+     *            Strings.
+     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
+     *            (excluding the ORDER BY itself). Passing null will use the
+     *            default sort order, which may be unordered.
+     */
+    public void startQuery(int token, Object cookie, Uri uri, String[] projection,
+            String selection, String[] selectionArgs, String orderBy) {
+        OperationInfo info = new OperationInfo();
+        info.op = Operation.EVENT_ARG_QUERY;
+        info.resolver = mContext.getContentResolver();
+
+        info.handler = mHandler;
+        info.token = token;
+        info.cookie = cookie;
+        info.uri = uri;
+        info.projection = projection;
+        info.selection = selection;
+        info.selectionArgs = selectionArgs;
+        info.orderBy = orderBy;
+
+        AsyncQueryServiceHelper.queueOperation(mContext, info);
+    }
+
+    /**
+     * This method begins an asynchronous insert. When the insert operation is
+     * done {@link #onInsertComplete} is called.
+     *
+     * @param token A token passed into {@link #onInsertComplete} to identify
+     *            the insert operation.
+     * @param cookie An object that gets passed into {@link #onInsertComplete}
+     * @param uri the Uri passed to the insert operation.
+     * @param initialValues the ContentValues parameter passed to the insert
+     *            operation.
+     * @param delayMillis delay in executing the operation. This operation will
+     *            execute before the delayed time when another operation is
+     *            added. Useful for implementing single level undo.
+     */
+    public void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues,
+            long delayMillis) {
+        OperationInfo info = new OperationInfo();
+        info.op = Operation.EVENT_ARG_INSERT;
+        info.resolver = mContext.getContentResolver();
+        info.handler = mHandler;
+
+        info.token = token;
+        info.cookie = cookie;
+        info.uri = uri;
+        info.values = initialValues;
+        info.delayMillis = delayMillis;
+
+        AsyncQueryServiceHelper.queueOperation(mContext, info);
+    }
+
+    /**
+     * This method begins an asynchronous update. When the update operation is
+     * done {@link #onUpdateComplete} is called.
+     *
+     * @param token A token passed into {@link #onUpdateComplete} to identify
+     *            the update operation.
+     * @param cookie An object that gets passed into {@link #onUpdateComplete}
+     * @param uri the Uri passed to the update operation.
+     * @param values the ContentValues parameter passed to the update operation.
+     * @param selection A filter declaring which rows to update, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will update all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *            replaced by the values from selectionArgs, in the order that
+     *            they appear in the selection. The values will be bound as
+     *            Strings.
+     * @param delayMillis delay in executing the operation. This operation will
+     *            execute before the delayed time when another operation is
+     *            added. Useful for implementing single level undo.
+     */
+    public void startUpdate(int token, Object cookie, Uri uri, ContentValues values,
+            String selection, String[] selectionArgs, long delayMillis) {
+        OperationInfo info = new OperationInfo();
+        info.op = Operation.EVENT_ARG_UPDATE;
+        info.resolver = mContext.getContentResolver();
+        info.handler = mHandler;
+
+        info.token = token;
+        info.cookie = cookie;
+        info.uri = uri;
+        info.values = values;
+        info.selection = selection;
+        info.selectionArgs = selectionArgs;
+        info.delayMillis = delayMillis;
+
+        AsyncQueryServiceHelper.queueOperation(mContext, info);
+    }
+
+    /**
+     * This method begins an asynchronous delete. When the delete operation is
+     * done {@link #onDeleteComplete} is called.
+     *
+     * @param token A token passed into {@link #onDeleteComplete} to identify
+     *            the delete operation.
+     * @param cookie An object that gets passed into {@link #onDeleteComplete}
+     * @param uri the Uri passed to the delete operation.
+     * @param selection A filter declaring which rows to delete, formatted as an
+     *            SQL WHERE clause (excluding the WHERE itself). Passing null
+     *            will delete all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be
+     *            replaced by the values from selectionArgs, in the order that
+     *            they appear in the selection. The values will be bound as
+     *            Strings.
+     * @param delayMillis delay in executing the operation. This operation will
+     *            execute before the delayed time when another operation is
+     *            added. Useful for implementing single level undo.
+     */
+    public void startDelete(int token, Object cookie, Uri uri, String selection,
+            String[] selectionArgs, long delayMillis) {
+        OperationInfo info = new OperationInfo();
+        info.op = Operation.EVENT_ARG_DELETE;
+        info.resolver = mContext.getContentResolver();
+        info.handler = mHandler;
+
+        info.token = token;
+        info.cookie = cookie;
+        info.uri = uri;
+        info.selection = selection;
+        info.selectionArgs = selectionArgs;
+        info.delayMillis = delayMillis;
+
+        AsyncQueryServiceHelper.queueOperation(mContext, info);
+    }
+
+    /**
+     * This method begins an asynchronous {@link ContentProviderOperation}. When
+     * the operation is done {@link #onBatchComplete} is called.
+     *
+     * @param token A token passed into {@link #onDeleteComplete} to identify
+     *            the delete operation.
+     * @param cookie An object that gets passed into {@link #onDeleteComplete}
+     * @param authority the authority used for the
+     *            {@link ContentProviderOperation}.
+     * @param cpo the {@link ContentProviderOperation} to be executed.
+     * @param delayMillis delay in executing the operation. This operation will
+     *            execute before the delayed time when another operation is
+     *            added. Useful for implementing single level undo.
+     */
+    public void startBatch(int token, Object cookie, String authority,
+            ArrayList<ContentProviderOperation> cpo, long delayMillis) {
+        OperationInfo info = new OperationInfo();
+        info.op = Operation.EVENT_ARG_BATCH;
+        info.resolver = mContext.getContentResolver();
+        info.handler = mHandler;
+
+        info.token = token;
+        info.cookie = cookie;
+        info.authority = authority;
+        info.cpo = cpo;
+        info.delayMillis = delayMillis;
+
+        AsyncQueryServiceHelper.queueOperation(mContext, info);
+    }
+
+    /**
+     * Called when an asynchronous query is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *            {@link #startQuery}.
+     * @param cookie the cookie object passed in from {@link #startQuery}.
+     * @param cursor The cursor holding the results from the query.
+     */
+    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+        if (localLOGV) {
+            Log.d(TAG, "########## default onQueryComplete");
+        }
+    }
+
+    /**
+     * Called when an asynchronous insert is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *            {@link #startInsert}.
+     * @param cookie the cookie object that's passed in from
+     *            {@link #startInsert}.
+     * @param uri the uri returned from the insert operation.
+     */
+    protected void onInsertComplete(int token, Object cookie, Uri uri) {
+        if (localLOGV) {
+            Log.d(TAG, "########## default onInsertComplete");
+        }
+    }
+
+    /**
+     * Called when an asynchronous update is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *            {@link #startUpdate}.
+     * @param cookie the cookie object that's passed in from
+     *            {@link #startUpdate}.
+     * @param result the result returned from the update operation
+     */
+    protected void onUpdateComplete(int token, Object cookie, int result) {
+        if (localLOGV) {
+            Log.d(TAG, "########## default onUpdateComplete");
+        }
+    }
+
+    /**
+     * Called when an asynchronous delete is completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *            {@link #startDelete}.
+     * @param cookie the cookie object that's passed in from
+     *            {@link #startDelete}.
+     * @param result the result returned from the delete operation
+     */
+    protected void onDeleteComplete(int token, Object cookie, int result) {
+        if (localLOGV) {
+            Log.d(TAG, "########## default onDeleteComplete");
+        }
+    }
+
+    /**
+     * Called when an asynchronous {@link ContentProviderOperation} is
+     * completed.
+     *
+     * @param token the token to identify the query, passed in from
+     *            {@link #startDelete}.
+     * @param cookie the cookie object that's passed in from
+     *            {@link #startDelete}.
+     * @param results the result returned from executing the
+     *            {@link ContentProviderOperation}
+     */
+    protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) {
+        if (localLOGV) {
+            Log.d(TAG, "########## default onBatchComplete");
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        OperationInfo info = (OperationInfo) msg.obj;
+
+        int token = msg.what;
+        int op = msg.arg1;
+
+        if (localLOGV) {
+            Log.d(TAG, "AsyncQueryService.handleMessage: token=" + token + ", op=" + op
+                    + ", result=" + info.result);
+        }
+
+        // pass token back to caller on each callback.
+        switch (op) {
+            case Operation.EVENT_ARG_QUERY:
+                onQueryComplete(token, info.cookie, (Cursor) info.result);
+                break;
+
+            case Operation.EVENT_ARG_INSERT:
+                onInsertComplete(token, info.cookie, (Uri) info.result);
+                break;
+
+            case Operation.EVENT_ARG_UPDATE:
+                onUpdateComplete(token, info.cookie, (Integer) info.result);
+                break;
+
+            case Operation.EVENT_ARG_DELETE:
+                onDeleteComplete(token, info.cookie, (Integer) info.result);
+                break;
+
+            case Operation.EVENT_ARG_BATCH:
+                onBatchComplete(token, info.cookie, (ContentProviderResult[]) info.result);
+                break;
+        }
+    }
+
+//    @VisibleForTesting
+    protected void setTestHandler(Handler handler) {
+        mHandler = handler;
+    }
+}
diff --git a/src/com/android/calendar/AsyncQueryServiceHelper.java b/src/com/android/calendar/AsyncQueryServiceHelper.java
new file mode 100644
index 0000000..36ee581
--- /dev/null
+++ b/src/com/android/calendar/AsyncQueryServiceHelper.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import com.android.calendar.AsyncQueryService.Operation;
+
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.PriorityQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+public class AsyncQueryServiceHelper extends IntentService {
+    private static final String TAG = "AsyncQuery";
+
+    private static final PriorityQueue<OperationInfo> sWorkQueue =
+        new PriorityQueue<OperationInfo>();
+
+    protected Class<AsyncQueryService> mService = AsyncQueryService.class;
+
+    protected static class OperationInfo implements Delayed{
+        public int token; // Used for cancel
+        public int op;
+        public ContentResolver resolver;
+        public Uri uri;
+        public String authority;
+        public Handler handler;
+        public String[] projection;
+        public String selection;
+        public String[] selectionArgs;
+        public String orderBy;
+        public Object result;
+        public Object cookie;
+        public ContentValues values;
+        public ArrayList<ContentProviderOperation> cpo;
+
+        /**
+         * delayMillis is relative time e.g. 10,000 milliseconds
+         */
+        public long delayMillis;
+
+        /**
+         * scheduleTimeMillis is the time scheduled for this to be processed.
+         * e.g. SystemClock.elapsedRealtime() + 10,000 milliseconds Based on
+         * {@link android.os.SystemClock#elapsedRealtime }
+         */
+        private long mScheduledTimeMillis = 0;
+
+        // @VisibleForTesting
+        void calculateScheduledTime() {
+            mScheduledTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
+        }
+
+        // @Override // Uncomment with Java6
+        public long getDelay(TimeUnit unit) {
+            return unit.convert(mScheduledTimeMillis - SystemClock.elapsedRealtime(),
+                    TimeUnit.MILLISECONDS);
+        }
+
+        // @Override // Uncomment with Java6
+        public int compareTo(Delayed another) {
+            OperationInfo anotherArgs = (OperationInfo) another;
+            if (this.mScheduledTimeMillis == anotherArgs.mScheduledTimeMillis) {
+                return 0;
+            } else if (this.mScheduledTimeMillis < anotherArgs.mScheduledTimeMillis) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("OperationInfo [\n\t token= ");
+            builder.append(token);
+            builder.append(",\n\t op= ");
+            builder.append(Operation.opToChar(op));
+            builder.append(",\n\t uri= ");
+            builder.append(uri);
+            builder.append(",\n\t authority= ");
+            builder.append(authority);
+            builder.append(",\n\t delayMillis= ");
+            builder.append(delayMillis);
+            builder.append(",\n\t mScheduledTimeMillis= ");
+            builder.append(mScheduledTimeMillis);
+            builder.append(",\n\t resolver= ");
+            builder.append(resolver);
+            builder.append(",\n\t handler= ");
+            builder.append(handler);
+            builder.append(",\n\t projection= ");
+            builder.append(Arrays.toString(projection));
+            builder.append(",\n\t selection= ");
+            builder.append(selection);
+            builder.append(",\n\t selectionArgs= ");
+            builder.append(Arrays.toString(selectionArgs));
+            builder.append(",\n\t orderBy= ");
+            builder.append(orderBy);
+            builder.append(",\n\t result= ");
+            builder.append(result);
+            builder.append(",\n\t cookie= ");
+            builder.append(cookie);
+            builder.append(",\n\t values= ");
+            builder.append(values);
+            builder.append(",\n\t cpo= ");
+            builder.append(cpo);
+            builder.append("\n]");
+            return builder.toString();
+        }
+
+        /**
+         * Compares an user-visible operation to this private OperationInfo
+         * object
+         *
+         * @param o operation to be compared
+         * @return true if logically equivalent
+         */
+        public boolean equivalent(Operation o) {
+            return o.token == this.token && o.op == this.op;
+        }
+    }
+
+    /**
+     * Queues the operation for execution
+     *
+     * @param context
+     * @param args OperationInfo object describing the operation
+     */
+    static public void queueOperation(Context context, OperationInfo args) {
+        // Set the schedule time for execution based on the desired delay.
+        args.calculateScheduledTime();
+
+        synchronized (sWorkQueue) {
+            sWorkQueue.add(args);
+            sWorkQueue.notify();
+        }
+
+        context.startService(new Intent(context, AsyncQueryServiceHelper.class));
+    }
+
+    /**
+     * Gets the last delayed operation. It is typically used for canceling.
+     *
+     * @return Operation object which contains of the last cancelable operation
+     */
+    static public Operation getLastCancelableOperation() {
+        long lastScheduleTime = Long.MIN_VALUE;
+        Operation op = null;
+
+        synchronized (sWorkQueue) {
+            // Unknown order even for a PriorityQueue
+            Iterator<OperationInfo> it = sWorkQueue.iterator();
+            while (it.hasNext()) {
+                OperationInfo info = it.next();
+                if (info.delayMillis > 0 && lastScheduleTime < info.mScheduledTimeMillis) {
+                    if (op == null) {
+                        op = new Operation();
+                    }
+
+                    op.token = info.token;
+                    op.op = info.op;
+                    op.scheduledExecutionTime = info.mScheduledTimeMillis;
+
+                    lastScheduleTime = info.mScheduledTimeMillis;
+                }
+            }
+        }
+
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "getLastCancelableOperation -> Operation:" + Operation.opToChar(op.op)
+                    + " token:" + op.token);
+        }
+        return op;
+    }
+
+    /**
+     * Attempts to cancel operation that has not already started. Note that
+     * there is no guarantee that the operation will be canceled. They still may
+     * result in a call to on[Query/Insert/Update/Delete/Batch]Complete after
+     * this call has completed.
+     *
+     * @param token The token representing the operation to be canceled. If
+     *            multiple operations have the same token they will all be
+     *            canceled.
+     */
+    static public int cancelOperation(int token) {
+        int canceled = 0;
+        synchronized (sWorkQueue) {
+            Iterator<OperationInfo> it = sWorkQueue.iterator();
+            while (it.hasNext()) {
+                if (it.next().token == token) {
+                    it.remove();
+                    ++canceled;
+                }
+            }
+        }
+
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "cancelOperation(" + token + ") -> " + canceled);
+        }
+        return canceled;
+    }
+
+    public AsyncQueryServiceHelper(String name) {
+        super(name);
+    }
+
+    public AsyncQueryServiceHelper() {
+        super("AsyncQueryServiceHelper");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        OperationInfo args;
+
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "onHandleIntent: queue size=" + sWorkQueue.size());
+        }
+        synchronized (sWorkQueue) {
+            while (true) {
+                /*
+                 * This method can be called with no work because of
+                 * cancellations
+                 */
+                if (sWorkQueue.size() == 0) {
+                    return;
+                } else if (sWorkQueue.size() == 1) {
+                    OperationInfo first = sWorkQueue.peek();
+                    long waitTime = first.mScheduledTimeMillis - SystemClock.elapsedRealtime();
+                    if (waitTime > 0) {
+                        try {
+                            sWorkQueue.wait(waitTime);
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                }
+
+                args = sWorkQueue.poll();
+                if (args != null) {
+                    // Got work to do. Break out of waiting loop
+                    break;
+                }
+            }
+        }
+
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "onHandleIntent: " + args);
+        }
+
+        ContentResolver resolver = args.resolver;
+        if (resolver != null) {
+
+            switch (args.op) {
+                case Operation.EVENT_ARG_QUERY:
+                    Cursor cursor;
+                    try {
+                        cursor = resolver.query(args.uri, args.projection, args.selection,
+                                args.selectionArgs, args.orderBy);
+                        /*
+                         * Calling getCount() causes the cursor window to be
+                         * filled, which will make the first access on the main
+                         * thread a lot faster
+                         */
+                        if (cursor != null) {
+                            cursor.getCount();
+                        }
+                    } catch (Exception e) {
+                        Log.w(TAG, e.toString());
+                        cursor = null;
+                    }
+
+                    args.result = cursor;
+                    break;
+
+                case Operation.EVENT_ARG_INSERT:
+                    args.result = resolver.insert(args.uri, args.values);
+                    break;
+
+                case Operation.EVENT_ARG_UPDATE:
+                    args.result = resolver.update(args.uri, args.values, args.selection,
+                            args.selectionArgs);
+                    break;
+
+                case Operation.EVENT_ARG_DELETE:
+                    args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
+                    break;
+
+                case Operation.EVENT_ARG_BATCH:
+                    try {
+                        args.result = resolver.applyBatch(args.authority, args.cpo);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, e.toString());
+                        args.result = null;
+                    } catch (OperationApplicationException e) {
+                        Log.e(TAG, e.toString());
+                        args.result = null;
+                    }
+                    break;
+            }
+
+            /*
+             * passing the original token value back to the caller on top of the
+             * event values in arg1.
+             */
+            Message reply = args.handler.obtainMessage(args.token);
+            reply.obj = args;
+            reply.arg1 = args.op;
+
+            if (AsyncQueryService.localLOGV) {
+                Log.d(TAG, "onHandleIntent: op=" + Operation.opToChar(args.op) + ", token="
+                        + reply.what);
+            }
+
+            reply.sendToTarget();
+        }
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "onStart startId=" + startId);
+        }
+        super.onStart(intent, startId);
+    }
+
+    @Override
+    public void onCreate() {
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "onCreate");
+        }
+        super.onCreate();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (AsyncQueryService.localLOGV) {
+            Log.d(TAG, "onDestroy");
+        }
+        super.onDestroy();
+    }
+}
diff --git a/src/com/android/calendar/CalendarActivity.java b/src/com/android/calendar/CalendarActivity.java
deleted file mode 100644
index 5eb9c45..0000000
--- a/src/com/android/calendar/CalendarActivity.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import dalvik.system.VMRuntime;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Calendar;
-import android.text.format.Time;
-import android.view.GestureDetector;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.TranslateAnimation;
-import android.widget.ProgressBar;
-import android.widget.ViewSwitcher;
-
-/**
- * This is the base class for Day and Week Activities.
- */
-public class CalendarActivity extends Activity implements Navigator {
-
-    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
-    private static final long ANIMATION_DURATION = 400;
-
-    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
-
-    private ContentResolver mContentResolver;
-
-    protected ProgressBar mProgressBar;
-    protected ViewSwitcher mViewSwitcher;
-    protected Animation mInAnimationForward;
-    protected Animation mOutAnimationForward;
-    protected Animation mInAnimationBackward;
-    protected Animation mOutAnimationBackward;
-    EventLoader mEventLoader;
-
-    Time mSelectedDay;
-
-    // This gets run if the time zone is updated in the db
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            // We want this to keep the same day so we swap the tz
-            mSelectedDay.timezone = Utils.getTimeZone(CalendarActivity.this, this);
-            mSelectedDay.normalize(true);
-        }
-    };
-
-    /* package */ GestureDetector mGestureDetector;
-
-    /**
-     * Listens for intent broadcasts
-     */
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_TIME_CHANGED)
-                    || action.equals(Intent.ACTION_DATE_CHANGED)
-                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
-                eventsChanged();
-            }
-        }
-    };
-
-    // Create an observer so that we can update the views whenever a
-    // Calendar event changes.
-    private ContentObserver mObserver = new ContentObserver(new Handler())
-    {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return true;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            eventsChanged();
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        mSelectedDay = new Time(Utils.getTimeZone(this, mUpdateTZ));
-
-        // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
-        // TODO: We should restore the old heap size once the activity reaches the idle state
-        VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
-
-        setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
-        mContentResolver = getContentResolver();
-
-        mInAnimationForward = AnimationUtils.loadAnimation(this, R.anim.slide_left_in);
-        mOutAnimationForward = AnimationUtils.loadAnimation(this, R.anim.slide_left_out);
-        mInAnimationBackward = AnimationUtils.loadAnimation(this, R.anim.slide_right_in);
-        mOutAnimationBackward = AnimationUtils.loadAnimation(this, R.anim.slide_right_out);
-
-        mGestureDetector = new GestureDetector(this, new CalendarGestureListener());
-        mEventLoader = new EventLoader(this);
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
-        super.onRestoreInstanceState(savedInstanceState);
-
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        Time time = new Time(Utils.getTimeZone(this, mUpdateTZ));
-        time.set(savedInstanceState.getLong(BUNDLE_KEY_RESTORE_TIME));
-        view.setSelectedDay(time);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        long timeMillis = Utils.timeFromIntentInMillis(intent);
-        if (timeMillis > 0) {
-            Time time = new Time(Utils.getTimeZone(this, mUpdateTZ));
-            time.set(timeMillis);
-            goTo(time, false);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mEventLoader.startBackgroundThread();
-        eventsChanged();
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.updateIs24HourFormat();
-        view.updateView();
-
-        view = (CalendarView) mViewSwitcher.getNextView();
-        view.updateIs24HourFormat();
-
-        // Register for Intent broadcasts
-        IntentFilter filter = new IntentFilter();
-
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_DATE_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        registerReceiver(mIntentReceiver, filter);
-
-        mContentResolver.registerContentObserver(Calendar.Events.CONTENT_URI,
-                true, mObserver);
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTimeInMillis());
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mContentResolver.unregisterContentObserver(mObserver);
-        unregisterReceiver(mIntentReceiver);
-
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.cleanup();
-        view = (CalendarView) mViewSwitcher.getNextView();
-        view.cleanup();
-        mEventLoader.stopBackgroundThread();
-    }
-
-    void startProgressSpinner() {
-        // start the progress spinner
-        mProgressBar.setVisibility(View.VISIBLE);
-    }
-
-    void stopProgressSpinner() {
-        // stop the progress spinner
-        mProgressBar.setVisibility(View.GONE);
-    }
-
-    /* Navigator interface methods */
-    public void goTo(Time time, boolean animate) {
-        if (animate) {
-            CalendarView current = (CalendarView) mViewSwitcher.getCurrentView();
-            if (current.getSelectedTime().before(time)) {
-                mViewSwitcher.setInAnimation(mInAnimationForward);
-                mViewSwitcher.setOutAnimation(mOutAnimationForward);
-            } else {
-                mViewSwitcher.setInAnimation(mInAnimationBackward);
-                mViewSwitcher.setOutAnimation(mOutAnimationBackward);
-            }
-        }
-
-        CalendarView next = (CalendarView) mViewSwitcher.getNextView();
-        next.setSelectedDay(time);
-        next.reloadEvents();
-        mViewSwitcher.showNext();
-        next.requestFocus();
-    }
-
-    /**
-     * Returns the selected time in milliseconds. The milliseconds are measured
-     * in UTC milliseconds from the epoch and uniquely specifies any selectable
-     * time.
-     *
-     * @return the selected time in milliseconds
-     */
-    public long getSelectedTimeInMillis() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        return view.getSelectedTimeInMillis();
-    }
-
-    public long getSelectedTime() {
-        return getSelectedTimeInMillis();
-    }
-
-    public void goToToday() {
-        mSelectedDay.set(System.currentTimeMillis());
-        mSelectedDay.normalize(true);
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.setSelectedDay(mSelectedDay);
-        view.reloadEvents();
-    }
-
-    public boolean getAllDay() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        return view.mSelectionAllDay;
-    }
-
-    void eventsChanged() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.clearCachedEvents();
-        view.reloadEvents();
-    }
-
-    Event getSelectedEvent() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        return view.getSelectedEvent();
-    }
-
-    boolean isEventSelected() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        return view.isEventSelected();
-    }
-
-    Event getNewEvent() {
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        return view.getNewEvent();
-    }
-
-    public CalendarView getNextView() {
-        return (CalendarView) mViewSwitcher.getNextView();
-    }
-
-    public View switchViews(boolean forward, float xOffSet, float width) {
-        float progress = Math.abs(xOffSet) / width;
-        if (progress > 1.0f) {
-            progress = 1.0f;
-        }
-
-        float inFromXValue, inToXValue;
-        float outFromXValue, outToXValue;
-        if (forward) {
-            inFromXValue = 1.0f - progress;
-            inToXValue = 0.0f;
-            outFromXValue = -progress;
-            outToXValue = -1.0f;
-        } else {
-            inFromXValue = progress - 1.0f;
-            inToXValue = 0.0f;
-            outFromXValue = progress;
-            outToXValue = 1.0f;
-        }
-
-        // We have to allocate these animation objects each time we switch views
-        // because that is the only way to set the animation parameters.
-        TranslateAnimation inAnimation = new TranslateAnimation(
-                Animation.RELATIVE_TO_SELF, inFromXValue,
-                Animation.RELATIVE_TO_SELF, inToXValue,
-                Animation.ABSOLUTE, 0.0f,
-                Animation.ABSOLUTE, 0.0f);
-
-        TranslateAnimation outAnimation = new TranslateAnimation(
-                Animation.RELATIVE_TO_SELF, outFromXValue,
-                Animation.RELATIVE_TO_SELF, outToXValue,
-                Animation.ABSOLUTE, 0.0f,
-                Animation.ABSOLUTE, 0.0f);
-
-        // Reduce the animation duration based on how far we have already swiped.
-        long duration = (long) (ANIMATION_DURATION * (1.0f - progress));
-        inAnimation.setDuration(duration);
-        outAnimation.setDuration(duration);
-        mViewSwitcher.setInAnimation(inAnimation);
-        mViewSwitcher.setOutAnimation(outAnimation);
-
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.cleanup();
-        mViewSwitcher.showNext();
-        view = (CalendarView) mViewSwitcher.getCurrentView();
-        view.requestFocus();
-        view.reloadEvents();
-        return view;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MenuHelper.onPrepareOptionsMenu(this, menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        if (! MenuHelper.onCreateOptionsMenu(menu)) {
-            return false;
-        }
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (MenuHelper.onOptionsItemSelected(this, item, this)) {
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (mGestureDetector.onTouchEvent(ev)) {
-            return true;
-        }
-        return super.onTouchEvent(ev);
-    }
-
-    class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
-        @Override
-        public boolean onSingleTapUp(MotionEvent ev) {
-            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-            view.doSingleTapUp(ev);
-            return true;
-        }
-
-        @Override
-        public void onLongPress(MotionEvent ev) {
-            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-            view.doLongPress(ev);
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-            view.doScroll(e1, e2, distanceX, distanceY);
-            return true;
-        }
-
-        @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-            view.doFling(e1, e2, velocityX, velocityY);
-            return true;
-        }
-
-        @Override
-        public boolean onDown(MotionEvent ev) {
-            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-            view.doDown(ev);
-            return true;
-        }
-    }
-}
-
diff --git a/src/com/android/calendar/CalendarApplication.java b/src/com/android/calendar/CalendarApplication.java
index 129b7c2..d0ca469 100644
--- a/src/com/android/calendar/CalendarApplication.java
+++ b/src/com/android/calendar/CalendarApplication.java
@@ -17,59 +17,8 @@
 package com.android.calendar;
 
 import android.app.Application;
-import android.content.Context;
-import android.preference.PreferenceManager;
 
 public class CalendarApplication extends Application {
-
-    // TODO: get rid of this global member.
-    public Event currentEvent = null;
-
-    /**
-     * The Screen class defines a node in a linked list.  This list contains
-     * the screens that were visited, with the more recently visited screens
-     * coming earlier in the list.  The "next" pointer of the head node
-     * points to the first element in the list (the most recently visited
-     * screen).
-     */
-    static class Screen {
-        public int id;
-        public Screen next;
-        public Screen previous;
-
-        public Screen(int id) {
-            this.id = id;
-            next = this;
-            previous = this;
-        }
-
-        // Adds the given node to the list after this one
-        public void insert(Screen node) {
-            node.next = next;
-            node.previous = this;
-            next.previous = node;
-            next = node;
-        }
-
-        // Removes this node from the list it is in.
-        public void unlink() {
-            next.previous = previous;
-            previous.next = next;
-        }
-    }
-
-    public static final int MONTH_VIEW_ID = 0;
-    public static final int WEEK_VIEW_ID = 1;
-    public static final int DAY_VIEW_ID = 2;
-    public static final int AGENDA_VIEW_ID = 3;
-
-    public static final String[] ACTIVITY_NAMES = new String[] {
-        MonthActivity.class.getName(),
-        WeekActivity.class.getName(),
-        DayActivity.class.getName(),
-        AgendaActivity.class.getName(),
-    };
-
     @Override
     public void onCreate() {
         super.onCreate();
@@ -78,7 +27,6 @@
          * Ensure the default values are set for any receiver, activity,
          * service, etc. of Calendar
          */
-        CalendarPreferenceActivity.setDefaultValues(this);
+        GeneralPreferences.setDefaultValues(this);
     }
-
 }
diff --git a/src/com/android/calendar/CalendarController.java b/src/com/android/calendar/CalendarController.java
new file mode 100644
index 0000000..23cf49a
--- /dev/null
+++ b/src/com/android/calendar/CalendarController.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+import static android.provider.Calendar.EVENT_END_TIME;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.text.TextUtils;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map.Entry;
+import java.util.WeakHashMap;
+
+public class CalendarController {
+    private static final boolean DEBUG = true;
+    private static final String TAG = "CalendarController";
+    private static final String REFRESH_SELECTION = Calendars.SYNC_EVENTS + "=?";
+    private static final String[] REFRESH_ARGS = new String[] { "1" };
+    private static final String REFRESH_ORDER = Calendars._SYNC_ACCOUNT + ","
+            + Calendars._SYNC_ACCOUNT_TYPE;
+
+    private Context mContext;
+
+    // This uses a LinkedHashMap so that we can replace fragments based on the
+    // view id they are being expanded into since we can't guarantee a reference
+    // to the handler will be findable
+    private LinkedHashMap<Integer,EventHandler> eventHandlers =
+            new LinkedHashMap<Integer,EventHandler>(5);
+    private LinkedList<Integer> mToBeRemovedEventHandlers = new LinkedList<Integer>();
+    private LinkedHashMap<Integer, EventHandler> mToBeAddedEventHandlers = new LinkedHashMap<
+            Integer, EventHandler>();
+    private boolean mDispatchInProgress;
+
+    private static WeakHashMap<Context, CalendarController> instances =
+        new WeakHashMap<Context, CalendarController>();
+
+    private WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1);
+
+    private int mViewType = -1;
+    private int mDetailViewType = -1;
+    private int mPreviousViewType = -1;
+    private long mEventId = -1;
+    private Time mTime = new Time();
+
+    private AsyncQueryService mService;
+
+    /**
+     * One of the event types that are sent to or from the controller
+     */
+    public interface EventType {
+        final long CREATE_EVENT = 1L;
+        final long VIEW_EVENT = 1L << 1;
+        final long EDIT_EVENT = 1L << 2;
+        final long DELETE_EVENT = 1L << 3;
+
+        final long GO_TO = 1L << 4;
+
+        final long LAUNCH_SETTINGS = 1L << 5;
+
+        final long EVENTS_CHANGED = 1L << 6;
+
+        final long SEARCH = 1L << 7;
+
+        // User has pressed the home key
+        final long USER_HOME = 1L << 8;
+    }
+
+    /**
+     * One of the Agenda/Day/Week/Month view types
+     */
+    public interface ViewType {
+        final int DETAIL = -1;
+        final int CURRENT = 0;
+        final int AGENDA = 1;
+        final int DAY = 2;
+        final int WEEK = 3;
+        final int MONTH = 4;
+        final int EDIT = 5;
+    }
+
+    public static class EventInfo {
+        public long eventType; // one of the EventType
+        public int viewType; // one of the ViewType
+        public long id; // event id
+        public Time selectedTime; // the selected time in focus
+        public Time startTime; // start of a range of time.
+        public Time endTime; // end of a range of time.
+        public int x; // x coordinate in the activity space
+        public int y; // y coordinate in the activity space
+        public String query; // query for a user search
+        public ComponentName componentName;  // used in combination with query
+    }
+
+    // FRAG_TODO remove unneeded api's
+    public interface EventHandler {
+        long getSupportedEventTypes();
+        void handleEvent(EventInfo event);
+
+        /**
+         * Returns the time in millis of the selected event in this view.
+         * @return the selected time in UTC milliseconds.
+         */
+        long getSelectedTime();
+
+        /**
+         * Changes the view to include the given time.
+         * @param time the desired time to view.
+         * @animate enable animation
+         */
+        void goTo(Time time, boolean animate);
+
+        /**
+         * Changes the view to include today's date.
+         */
+        void goToToday();
+
+        /**
+         * This is called when the user wants to create a new event and returns
+         * true if the new event should default to an all-day event.
+         * @return true if the new event should be an all-day event.
+         */
+        boolean getAllDay();
+
+        /**
+         * This notifies the handler that the database has changed and it should
+         * update its view.
+         */
+        void eventsChanged();
+    }
+
+    /**
+     * Creates and/or returns an instance of CalendarController associated with
+     * the supplied context. It is best to pass in the current Activity.
+     *
+     * @param context The activity if at all possible.
+     */
+    public static CalendarController getInstance(Context context) {
+        synchronized (instances) {
+            CalendarController controller = instances.get(context);
+            if (controller == null) {
+                controller = new CalendarController(context);
+                instances.put(context, controller);
+            }
+            return controller;
+        }
+    }
+
+    /**
+     * Removes an instance when it is no longer needed. This should be called in
+     * an activity's onDestroy method.
+     *
+     * @param context The activity used to create the controller
+     */
+    public static void removeInstance(Context context) {
+        instances.remove(context);
+    }
+
+    private CalendarController(Context context) {
+        mContext = context;
+        mTime.setToNow();
+        mDetailViewType = Utils.getSharedPreference(mContext,
+                GeneralPreferences.KEY_DETAILED_VIEW,
+                GeneralPreferences.DEFAULT_DETAILED_VIEW);
+        mService = new AsyncQueryService(context) {
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                new RefreshInBackground().execute(cursor);
+            }
+        };
+    }
+
+    /**
+     * Helper for sending New/View/Edit/Delete events
+     *
+     * @param sender object of the caller
+     * @param eventType one of {@link EventType}
+     * @param eventId event id
+     * @param startMillis start time
+     * @param endMillis end time
+     * @param x x coordinate in the activity space
+     * @param y y coordinate in the activity space
+     */
+    public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis,
+            long endMillis, int x, int y) {
+        EventInfo info = new EventInfo();
+        info.eventType = eventType;
+        if (eventType == EventType.EDIT_EVENT) {
+            info.viewType = ViewType.EDIT;
+        }
+        info.id = eventId;
+        info.startTime = new Time();
+        info.startTime.set(startMillis);
+        info.endTime = new Time();
+        info.endTime.set(endMillis);
+        info.x = x;
+        info.y = y;
+        this.sendEvent(sender, info);
+    }
+
+    /**
+     * Helper for sending non-calendar-event events
+     *
+     * @param sender object of the caller
+     * @param eventType one of {@link EventType}
+     * @param start start time
+     * @param end end time
+     * @param eventId event id
+     * @param viewType {@link ViewType}
+     */
+    public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
+            int viewType) {
+        sendEvent(sender, eventType, start, end, eventId, viewType, null, null);
+    }
+
+    /**
+     * sendEvent() variant mainly used for search.
+     */
+    public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
+            int viewType, String query, ComponentName componentName) {
+        EventInfo info = new EventInfo();
+        info.eventType = eventType;
+        info.startTime = start;
+        info.endTime = end;
+        info.id = eventId;
+        info.viewType = viewType;
+        info.query = query;
+        info.componentName = componentName;
+        this.sendEvent(sender, info);
+    }
+
+    public void sendEvent(Object sender, final EventInfo event) {
+        // TODO Throw exception on invalid events
+
+        if (DEBUG) {
+            Log.d(TAG, eventInfoToString(event));
+        }
+
+        Long filteredTypes = filters.get(sender);
+        if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) {
+            // Suppress event per filter
+            if (DEBUG) {
+                Log.d(TAG, "Event suppressed");
+            }
+            return;
+        }
+
+        mPreviousViewType = mViewType;
+
+        // Fix up view if not specified
+        if (event.viewType == ViewType.DETAIL) {
+            event.viewType = mDetailViewType;
+            mViewType = mDetailViewType;
+        } else if (event.viewType == ViewType.CURRENT) {
+            event.viewType = mViewType;
+        } else {
+            mViewType = event.viewType;
+
+            if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY) {
+                mDetailViewType = mViewType;
+            }
+        }
+
+        // Fix up start time if not specified
+        if (event.startTime != null && event.startTime.toMillis(false) != 0) {
+            mTime.set(event.startTime);
+        }
+        event.startTime = mTime;
+
+        // Store the eventId if we're entering edit event
+        if ((event.eventType & (EventType.CREATE_EVENT | EventType.EDIT_EVENT)) != 0 ||
+                (event.eventType == EventType.GO_TO && event.viewType == ViewType.EDIT)) {
+            if (event.id > 0) {
+                mEventId = event.id;
+            } else {
+                mEventId = -1;
+            }
+        }
+
+        boolean handled = false;
+        synchronized (this) {
+            mDispatchInProgress = true;
+
+            if (DEBUG) {
+                Log.d(TAG, "sendEvent: Dispatching to " + eventHandlers.size() + " handlers");
+            }
+            // Dispatch to event handler(s)
+            for (Iterator<Entry<Integer, EventHandler>> handlers =
+                    eventHandlers.entrySet().iterator(); handlers.hasNext();) {
+                Entry<Integer, EventHandler> entry = handlers.next();
+                int key = entry.getKey();
+                EventHandler eventHandler = entry.getValue();
+                if (eventHandler != null
+                        && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) {
+                    if (mToBeRemovedEventHandlers.contains(key)) {
+                        continue;
+                    }
+                    eventHandler.handleEvent(event);
+                    handled = true;
+                }
+            }
+
+            mDispatchInProgress = false;
+
+            // Deregister removed handlers
+            if (mToBeRemovedEventHandlers.size() > 0) {
+                for (Integer zombie : mToBeRemovedEventHandlers) {
+                    eventHandlers.remove(zombie);
+                }
+                mToBeRemovedEventHandlers.clear();
+            }
+            // Add new handlers
+            if (mToBeAddedEventHandlers.size() > 0) {
+                for (Entry<Integer, EventHandler> food : mToBeAddedEventHandlers.entrySet()) {
+                    eventHandlers.put(food.getKey(), food.getValue());
+                }
+            }
+        }
+
+        if (!handled) {
+            // Launch Settings
+            if (event.eventType == EventType.LAUNCH_SETTINGS) {
+                launchSettings();
+                return;
+            }
+
+            // Create/View/Edit/Delete Event
+            long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false);
+            if (event.eventType == EventType.CREATE_EVENT) {
+                launchCreateEvent(event.startTime.toMillis(false), endTime);
+                return;
+            } else if (event.eventType == EventType.VIEW_EVENT) {
+                launchViewEvent(event.id, event.startTime.toMillis(false), endTime);
+                return;
+            } else if (event.eventType == EventType.EDIT_EVENT) {
+                launchEditEvent(event.id, event.startTime.toMillis(false), endTime);
+                return;
+            } else if (event.eventType == EventType.DELETE_EVENT) {
+                launchDeleteEvent(event.id, event.startTime.toMillis(false), endTime);
+                return;
+            } else if (event.eventType == EventType.SEARCH) {
+                launchSearch(event.id, event.query, event.componentName);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Adds or updates an event handler. This uses a LinkedHashMap so that we can
+     * replace fragments based on the view id they are being expanded into.
+     *
+     * @param key The view id or placeholder for this handler
+     * @param eventHandler Typically a fragment or activity in the calendar app
+     */
+    public void registerEventHandler(int key, EventHandler eventHandler) {
+        synchronized (this) {
+            if (mDispatchInProgress) {
+                mToBeAddedEventHandlers.put(key, eventHandler);
+            } else {
+                eventHandlers.put(key, eventHandler);
+            }
+        }
+    }
+
+    public void deregisterEventHandler(Integer key) {
+        synchronized (this) {
+            if (mDispatchInProgress) {
+                // To avoid ConcurrencyException, stash away the event handler for now.
+                mToBeRemovedEventHandlers.add(key);
+            } else {
+                eventHandlers.remove(key);
+            }
+        }
+    }
+
+    // FRAG_TODO doesn't work yet
+    public void filterBroadcasts(Object sender, long eventTypes) {
+        filters.put(sender, eventTypes);
+    }
+
+    /**
+     * @return the time that this controller is currently pointed at
+     */
+    public long getTime() {
+        return mTime.toMillis(false);
+    }
+
+    /**
+     * @return the last event ID the edit view was launched with
+     */
+    public long getEventId() {
+        return mEventId;
+    }
+
+    public int getViewType() {
+        return mViewType;
+    }
+
+    public int getPreviousViewType() {
+        return mPreviousViewType;
+    }
+
+    private void launchSettings() {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClassName(mContext, CalendarSettingsActivity.class.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mContext.startActivity(intent);
+    }
+
+    private void launchCreateEvent(long startMillis, long endMillis) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClassName(mContext, EditEventActivity.class.getName());
+        intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+        intent.putExtra(EVENT_END_TIME, endMillis);
+        mEventId = -1;
+        mContext.startActivity(intent);
+    }
+
+    private void launchViewEvent(long eventId, long startMillis, long endMillis) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        intent.setData(eventUri);
+        intent.setClassName(mContext, EventInfoActivity.class.getName());
+        intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+        intent.putExtra(EVENT_END_TIME, endMillis);
+        mContext.startActivity(intent);
+    }
+
+    private void launchEditEvent(long eventId, long startMillis, long endMillis) {
+        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        Intent intent = new Intent(Intent.ACTION_EDIT, uri);
+        intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+        intent.putExtra(EVENT_END_TIME, endMillis);
+        intent.setClass(mContext, EditEventActivity.class);
+        mEventId = eventId;
+        mContext.startActivity(intent);
+    }
+
+    private void launchDeleteEvent(long eventId, long startMillis, long endMillis) {
+        launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1);
+    }
+
+    private void launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis,
+            long endMillis, int deleteWhich) {
+        DeleteEventHelper deleteEventHelper = new DeleteEventHelper(mContext, parentActivity,
+                parentActivity != null /* exit when done */);
+        deleteEventHelper.delete(startMillis, endMillis, eventId, deleteWhich);
+    }
+
+    private void launchSearch(long eventId, String query, ComponentName componentName) {
+        final SearchManager searchManager =
+                (SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE);
+        final SearchableInfo searchableInfo = searchManager.getSearchableInfo(componentName);
+        final Intent intent = new Intent(Intent.ACTION_SEARCH);
+        intent.putExtra(SearchManager.QUERY, query);
+        intent.setComponent(searchableInfo.getSearchActivity());
+        mContext.startActivity(intent);
+    }
+
+    public void refreshCalendars() {
+        Log.d(TAG, "RefreshCalendars starting");
+        // get the account, url, and current sync state
+        mService.startQuery(mService.getNextToken(), null, Calendars.CONTENT_URI,
+                new String[] {Calendars._ID, // 0
+                        Calendars._SYNC_ACCOUNT, // 1
+                        Calendars._SYNC_ACCOUNT_TYPE, // 2
+                        },
+                REFRESH_SELECTION, REFRESH_ARGS, REFRESH_ORDER);
+    }
+
+    // Forces the viewType. Should only be used for initialization.
+    public void setViewType(int viewType) {
+        mViewType = viewType;
+    }
+
+    // Sets the eventId. Should only be used for initialization.
+    public void setEventId(long eventId) {
+        mEventId = eventId;
+    }
+
+    private class RefreshInBackground extends AsyncTask<Cursor, Integer, Integer> {
+        /* (non-Javadoc)
+         * @see android.os.AsyncTask#doInBackground(Params[])
+         */
+        @Override
+        protected Integer doInBackground(Cursor... params) {
+            if (params.length != 1) {
+                return null;
+            }
+            Cursor cursor = params[0];
+            if (cursor == null) {
+                return null;
+            }
+
+            String previousAccount = null;
+            String previousType = null;
+            Log.d(TAG, "Refreshing " + cursor.getCount() + " calendars");
+            try {
+                while (cursor.moveToNext()) {
+                    Account account = null;
+                    String accountName = cursor.getString(1);
+                    String accountType = cursor.getString(2);
+                    // Only need to schedule one sync per account and they're
+                    // ordered by account,type
+                    if (TextUtils.equals(accountName, previousAccount) &&
+                            TextUtils.equals(accountType, previousType)) {
+                        continue;
+                    }
+                    previousAccount = accountName;
+                    previousType = accountType;
+                    account = new Account(accountName, accountType);
+                    scheduleSync(account, false /* two-way sync */, null);
+                }
+            } finally {
+                cursor.close();
+            }
+            return null;
+        }
+
+        /**
+         * Schedule a calendar sync for the account.
+         * @param account the account for which to schedule a sync
+         * @param uploadChangesOnly if set, specify that the sync should only send
+         *   up local changes.  This is typically used for a local sync, a user override of
+         *   too many deletions, or a sync after a calendar is unselected.
+         * @param url the url feed for the calendar to sync (may be null, in which case a poll of
+         *   all feeds is done.)
+         */
+        void scheduleSync(Account account, boolean uploadChangesOnly, String url) {
+            Bundle extras = new Bundle();
+            if (uploadChangesOnly) {
+                extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly);
+            }
+            if (url != null) {
+                extras.putString("feed", url);
+                extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+            }
+            ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras);
+        }
+    }
+
+    private String eventInfoToString(EventInfo eventInfo) {
+        String tmp = "Unknown";
+
+        StringBuilder builder = new StringBuilder();
+        if ((eventInfo.eventType & EventType.GO_TO) != 0) {
+            tmp = "Go to time/event";
+        } else if ((eventInfo.eventType & EventType.CREATE_EVENT) != 0) {
+            tmp = "New event";
+        } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) {
+            tmp = "View event";
+        } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) {
+            tmp = "Edit event";
+        } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) {
+            tmp = "Delete event";
+        } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) {
+            tmp = "Launch settings";
+        } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) {
+            tmp = "Refresh events";
+        } else if ((eventInfo.eventType & EventType.SEARCH) != 0) {
+            tmp = "Search";
+        }
+        builder.append(tmp);
+        builder.append(": id=");
+        builder.append(eventInfo.id);
+        builder.append(", selected=");
+        builder.append(eventInfo.selectedTime);
+        builder.append(", start=");
+        builder.append(eventInfo.startTime);
+        builder.append(", end=");
+        builder.append(eventInfo.endTime);
+        builder.append(", viewType=");
+        builder.append(eventInfo.viewType);
+        builder.append(", x=");
+        builder.append(eventInfo.x);
+        builder.append(", y=");
+        builder.append(eventInfo.y);
+        return builder.toString();
+    }
+}
diff --git a/src/com/android/calendar/CalendarData.java b/src/com/android/calendar/CalendarData.java
index 49f3056..5c8456f 100644
--- a/src/com/android/calendar/CalendarData.java
+++ b/src/com/android/calendar/CalendarData.java
@@ -17,24 +17,6 @@
 package com.android.calendar;
 
 public final class CalendarData {
-    static final String[] sDateStrings = { "0", "1", "2", "3", "4", "5", "6",
-        "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17",
-        "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28",
-        "29", "30", "31" };
-
-    static final String[] sMonthNumStrings = { "1", "2", "3", "4", "5", "6",
-        "7", "8", "9", "10", "11", "12" };
-
-    static final String[] s12Hours = { "12 AM", "1 AM", "2 AM", "3 AM", "4 AM",
-        "5 AM", "6 AM", "7 AM", "8 AM", "9 AM", "10 AM", "11 AM", "Noon",
-        "1 PM", "2 PM", "3 PM", "4 PM", "5 PM", "6 PM", "7 PM", "8 PM",
-        "9 PM", "10 PM", "11 PM", "12 AM" };
-
-    static final String[] s12AmPm = { "AM", "AM", "AM", "AM", "AM",
-        "AM", "AM", "AM", "AM", "AM", "AM", "AM", "PM",
-        "PM", "PM", "PM", "PM", "PM", "PM", "PM", "PM",
-        "PM", "PM", "PM", "AM" };
-
     static final String[] s12HoursNoAmPm = { "12", "1", "2", "3", "4",
         "5", "6", "7", "8", "9", "10", "11", "12",
         "1", "2", "3", "4", "5", "6", "7", "8",
@@ -43,13 +25,4 @@
     static final String[] s24Hours = { "00", "01", "02", "03", "04", "05",
         "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16",
         "17", "18", "19", "20", "21", "22", "23", "00" };
-    
-    static final String[] sMinutes = {
-        ":00", ":01", ":02", ":03", ":04", ":05", ":06", ":07", ":08", ":09",
-        ":10", ":11", ":12", ":13", ":14", ":15", ":16", ":17", ":18", ":19",
-        ":20", ":21", ":22", ":23", ":24", ":25", ":26", ":27", ":28", ":29",
-        ":30", ":31", ":32", ":33", ":34", ":35", ":36", ":37", ":38", ":39",
-        ":40", ":41", ":42", ":43", ":44", ":45", ":46", ":47", ":48", ":49",
-        ":50", ":51", ":52", ":53", ":54", ":55", ":56", ":57", ":58", ":59"
-    };
 }
diff --git a/src/com/android/calendar/CalendarEventModel.java b/src/com/android/calendar/CalendarEventModel.java
new file mode 100644
index 0000000..3d382bd
--- /dev/null
+++ b/src/com/android/calendar/CalendarEventModel.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Events;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.TimeZone;
+
+/**
+ * Stores all the information needed to fill out an entry in the events table.
+ * This is a convenient way for storing information needed by the UI to write to
+ * the events table. Only fields that are important to the UI are included.
+ */
+public class CalendarEventModel {
+    public static class Attendee {
+        @Override
+        public int hashCode() {
+            return (mEmail == null) ? 0 : mEmail.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (!(obj instanceof Attendee)) {
+                return false;
+            }
+            Attendee other = (Attendee) obj;
+            if (!TextUtils.equals(mEmail, other.mEmail)) {
+                return false;
+            }
+            return true;
+        }
+
+        public String mName;
+        public String mEmail;
+        public int mStatus;
+
+        public Attendee(String name, String email) {
+            mName = name;
+            mEmail = email;
+            mStatus = Attendees.ATTENDEE_STATUS_NONE;
+        }
+    }
+
+    // TODO strip out fields that don't ever get used
+    /**
+     * The uri of the event in the db. This should only be null for new events.
+     */
+    public Uri mUri = null;
+    public long mId = -1;
+    public long mCalendarId = -1;
+    public String mSyncId = null;
+    public String mSyncAccount = null;
+    public String mSyncAccountType = null;
+
+    // PROVIDER_NOTES owner account comes from the calendars table
+    public String mOwnerAccount = null;
+    public String mTitle = null;
+    public String mLocation = null;
+    public String mDescription = null;
+    public String mRrule = null;
+    public String mOrganizer = null;
+    public boolean mIsOrganizer = true;
+    public boolean mIsFirstEventInSeries = true;
+
+    // This should be set the same as mStart when created and is used for making changes to
+    // recurring events. It should not be updated after it is initially set.
+    public long mOriginalStart = -1;
+    public long mStart = -1;
+
+    // This should be set the same as mEnd when created and is used for making changes to
+    // recurring events. It should not be updated after it is initially set.
+    public long mOriginalEnd = -1;
+    public long mEnd = -1;
+    public String mDuration = null;
+    public String mTimezone = null;
+    public String mTimezone2 = null;
+    public boolean mAllDay = false;
+    public boolean mHasAlarm = false;
+    public boolean mTransparency = false;
+
+    // PROVIDER_NOTES How does an event not have attendee data? The owner is added
+    // as an attendee by default.
+    public boolean mHasAttendeeData = true;
+    public int mSelfAttendeeStatus = -1;
+    public int mOwnerAttendeeId = -1;
+    public String mOriginalEvent = null;
+    public Long mOriginalTime = null;
+    public Boolean mOriginalAllDay = null;
+    public boolean mGuestsCanModify = false;
+    public boolean mGuestsCanInviteOthers = false;
+    public boolean mGuestsCanSeeGuests = false;
+
+    public int mVisibility = 0;
+    public ArrayList<Integer> mReminderMinutes;
+
+    // PROVIDER_NOTES Using EditEventHelper the owner should not be included in this
+    // list and will instead be added by saveEvent. Is this what we want?
+    public LinkedHashMap<String, Attendee> mAttendeesList;
+
+    public CalendarEventModel() {
+        mReminderMinutes = new ArrayList<Integer>();
+        mAttendeesList = new LinkedHashMap<String, Attendee>();
+        mTimezone = TimeZone.getDefault().getID();
+    }
+
+    public CalendarEventModel(Context context) {
+        this();
+
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+        String defaultReminder = prefs.getString(GeneralPreferences.KEY_DEFAULT_REMINDER,
+                "0");
+        int defaultReminderMins = Integer.parseInt(defaultReminder);
+        if (defaultReminderMins != 0) {
+            mHasAlarm = true;
+            mReminderMinutes.add(defaultReminderMins);
+        }
+    }
+
+    public CalendarEventModel(Context context, Intent intent) {
+        this(context);
+
+        String title = intent.getStringExtra(Events.TITLE);
+        if (title != null) {
+            mTitle = title;
+        }
+
+        String location = intent.getStringExtra(Events.EVENT_LOCATION);
+        if (location != null) {
+            mLocation = location;
+        }
+
+        String description = intent.getStringExtra(Events.DESCRIPTION);
+        if (description != null) {
+            mDescription = description;
+        }
+
+        int transparency = intent.getIntExtra(Events.TRANSPARENCY, -1);
+        if (transparency != -1) {
+            mTransparency = transparency != 0;
+        }
+
+        int visibility = intent.getIntExtra(Events.VISIBILITY, -1);
+        if (visibility != -1) {
+            mVisibility = visibility;
+        }
+
+        String rrule = intent.getStringExtra(Events.RRULE);
+        if (rrule != null) {
+            mRrule = rrule;
+        }
+    }
+
+    public boolean isValid() {
+        if (mCalendarId == -1) {
+            return false;
+        }
+        if (TextUtils.isEmpty(mOwnerAccount)) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean isEmpty() {
+        if (mTitle.length() > 0) {
+            return false;
+        }
+
+        if (mLocation.length() > 0) {
+            return false;
+        }
+
+        if (mDescription.length() > 0) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public void clear() {
+        mUri = null;
+        mId = -1;
+        mCalendarId = -1;
+
+        mSyncId = null;
+        mSyncAccount = null;
+        mSyncAccountType = null;
+        mOwnerAccount = null;
+
+        mTitle = null;
+        mLocation = null;
+        mDescription = null;
+        mRrule = null;
+        mOrganizer = null;
+        mIsOrganizer = true;
+        mIsFirstEventInSeries = true;
+
+        mOriginalStart = -1;
+        mStart = -1;
+        mOriginalEnd = -1;
+        mEnd = -1;
+        mDuration = null;
+        mTimezone = null;
+        mTimezone2 = null;
+        mAllDay = false;
+        mHasAlarm = false;
+
+        mHasAttendeeData = true;
+        mSelfAttendeeStatus = -1;
+        mOwnerAttendeeId = -1;
+        mOriginalEvent = null;
+        mOriginalTime = null;
+        mOriginalAllDay = null;
+
+        mGuestsCanModify = false;
+        mGuestsCanInviteOthers = false;
+        mGuestsCanSeeGuests = false;
+        mVisibility = 0;
+
+        mReminderMinutes = new ArrayList<Integer>();
+        mAttendeesList.clear();
+    }
+
+    public void addAttendee(Attendee attendee) {
+        mAttendeesList.put(attendee.mEmail, attendee);
+    }
+
+    public void removeAttendee(Attendee attendee) {
+        mAttendeesList.remove(attendee.mEmail);
+    }
+
+    public String getAttendeesString() {
+        StringBuilder b = new StringBuilder();
+        for (Attendee attendee : mAttendeesList.values()) {
+            String name = attendee.mName;
+            String email = attendee.mEmail;
+            String status = Integer.toString(attendee.mStatus);
+            b.append("name:").append(name);
+            b.append(" email:").append(email);
+            b.append(" status:").append(status);
+        }
+        return b.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (mAllDay ? 1231 : 1237);
+        result = prime * result + ((mAttendeesList == null) ? 0 : getAttendeesString().hashCode());
+        result = prime * result + (int) (mCalendarId ^ (mCalendarId >>> 32));
+        result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
+        result = prime * result + ((mDuration == null) ? 0 : mDuration.hashCode());
+        result = prime * result + (int) (mEnd ^ (mEnd >>> 32));
+        result = prime * result + (mGuestsCanInviteOthers ? 1231 : 1237);
+        result = prime * result + (mGuestsCanModify ? 1231 : 1237);
+        result = prime * result + (mGuestsCanSeeGuests ? 1231 : 1237);
+        result = prime * result + (mHasAlarm ? 1231 : 1237);
+        result = prime * result + (mHasAttendeeData ? 1231 : 1237);
+        result = prime * result + (int) (mId ^ (mId >>> 32));
+        result = prime * result + (mIsFirstEventInSeries ? 1231 : 1237);
+        result = prime * result + (mIsOrganizer ? 1231 : 1237);
+        result = prime * result + ((mLocation == null) ? 0 : mLocation.hashCode());
+        result = prime * result + ((mOrganizer == null) ? 0 : mOrganizer.hashCode());
+        result = prime * result + ((mOriginalAllDay == null) ? 0 : mOriginalAllDay.hashCode());
+        result = prime * result + (int) (mOriginalEnd ^ (mOriginalEnd >>> 32));
+        result = prime * result + ((mOriginalEvent == null) ? 0 : mOriginalEvent.hashCode());
+        result = prime * result + (int) (mOriginalStart ^ (mOriginalStart >>> 32));
+        result = prime * result + ((mOriginalTime == null) ? 0 : mOriginalTime.hashCode());
+        result = prime * result + ((mOwnerAccount == null) ? 0 : mOwnerAccount.hashCode());
+        result = prime * result + ((mReminderMinutes == null) ? 0 : mReminderMinutes.hashCode());
+        result = prime * result + ((mRrule == null) ? 0 : mRrule.hashCode());
+        result = prime * result + mSelfAttendeeStatus;
+        result = prime * result + mOwnerAttendeeId;
+        result = prime * result + (int) (mStart ^ (mStart >>> 32));
+        result = prime * result + ((mSyncAccount == null) ? 0 : mSyncAccount.hashCode());
+        result = prime * result + ((mSyncAccountType == null) ? 0 : mSyncAccountType.hashCode());
+        result = prime * result + ((mSyncId == null) ? 0 : mSyncId.hashCode());
+        result = prime * result + ((mTimezone == null) ? 0 : mTimezone.hashCode());
+        result = prime * result + ((mTimezone2 == null) ? 0 : mTimezone2.hashCode());
+        result = prime * result + ((mTitle == null) ? 0 : mTitle.hashCode());
+        result = prime * result + (mTransparency ? 1231 : 1237);
+        result = prime * result + ((mUri == null) ? 0 : mUri.hashCode());
+        result = prime * result + mVisibility;
+        return result;
+    }
+
+    // Autogenerated equals method
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof CalendarEventModel)) {
+            return false;
+        }
+
+        CalendarEventModel other = (CalendarEventModel) obj;
+        if (mAllDay != other.mAllDay) {
+            return false;
+        }
+        if (mAttendeesList == null) {
+            if (other.mAttendeesList != null) {
+                return false;
+            }
+        } else if (!TextUtils.equals(getAttendeesString(), other.getAttendeesString())) {
+            return false;
+        }
+
+        if (mCalendarId != other.mCalendarId) {
+            return false;
+        }
+
+        if (mDescription == null) {
+            if (other.mDescription != null) {
+                return false;
+            }
+        } else if (!mDescription.equals(other.mDescription)) {
+            return false;
+        }
+
+        if (mDuration == null) {
+            if (other.mDuration != null) {
+                return false;
+            }
+        } else if (!mDuration.equals(other.mDuration)) {
+            return false;
+        }
+
+        if (mEnd != other.mEnd) {
+            return false;
+        }
+        if (mGuestsCanInviteOthers != other.mGuestsCanInviteOthers) {
+            return false;
+        }
+        if (mGuestsCanModify != other.mGuestsCanModify) {
+            return false;
+        }
+        if (mGuestsCanSeeGuests != other.mGuestsCanSeeGuests) {
+            return false;
+        }
+        if (mHasAlarm != other.mHasAlarm) {
+            return false;
+        }
+        if (mHasAttendeeData != other.mHasAttendeeData) {
+            return false;
+        }
+        if (mId != other.mId) {
+            return false;
+        }
+        if (mIsFirstEventInSeries != other.mIsFirstEventInSeries) {
+            return false;
+        }
+        if (mIsOrganizer != other.mIsOrganizer) {
+            return false;
+        }
+
+        if (mLocation == null) {
+            if (other.mLocation != null) {
+                return false;
+            }
+        } else if (!mLocation.equals(other.mLocation)) {
+            return false;
+        }
+
+        if (mOrganizer == null) {
+            if (other.mOrganizer != null) {
+                return false;
+            }
+        } else if (!mOrganizer.equals(other.mOrganizer)) {
+            return false;
+        }
+
+        if (mOriginalAllDay == null) {
+            if (other.mOriginalAllDay != null) {
+                return false;
+            }
+        } else if (!mOriginalAllDay.equals(other.mOriginalAllDay)) {
+            return false;
+        }
+
+        if (mOriginalEnd != other.mOriginalEnd) {
+            return false;
+        }
+
+        if (mOriginalEvent == null) {
+            if (other.mOriginalEvent != null) {
+                return false;
+            }
+        } else if (!mOriginalEvent.equals(other.mOriginalEvent)) {
+            return false;
+        }
+
+        if (mOriginalStart != other.mOriginalStart) {
+            return false;
+        }
+
+        if (mOriginalTime == null) {
+            if (other.mOriginalTime != null) {
+                return false;
+            }
+        } else if (!mOriginalTime.equals(other.mOriginalTime)) {
+            return false;
+        }
+
+        if (mOwnerAccount == null) {
+            if (other.mOwnerAccount != null) {
+                return false;
+            }
+        } else if (!mOwnerAccount.equals(other.mOwnerAccount)) {
+            return false;
+        }
+
+        if (mReminderMinutes == null) {
+            if (other.mReminderMinutes != null) {
+                return false;
+            }
+        } else if (!mReminderMinutes.equals(other.mReminderMinutes)) {
+            return false;
+        }
+
+        if (mRrule == null) {
+            if (other.mRrule != null) {
+                return false;
+            }
+        } else if (!mRrule.equals(other.mRrule)) {
+            return false;
+        }
+
+        if (mSelfAttendeeStatus != other.mSelfAttendeeStatus) {
+            return false;
+        }
+        if (mOwnerAttendeeId != other.mOwnerAttendeeId) {
+            return false;
+        }
+        if (mStart != other.mStart) {
+            return false;
+        }
+        if (mSyncAccount == null) {
+            if (other.mSyncAccount != null) {
+                return false;
+            }
+        } else if (!mSyncAccount.equals(other.mSyncAccount)) {
+            return false;
+        }
+
+        if (mSyncAccountType == null) {
+            if (other.mSyncAccountType != null) {
+                return false;
+            }
+        } else if (!mSyncAccountType.equals(other.mSyncAccountType)) {
+            return false;
+        }
+
+        if (mSyncId == null) {
+            if (other.mSyncId != null) {
+                return false;
+            }
+        } else if (!mSyncId.equals(other.mSyncId)) {
+            return false;
+        }
+
+        if (mTimezone == null) {
+            if (other.mTimezone != null) {
+                return false;
+            }
+        } else if (!mTimezone.equals(other.mTimezone)) {
+            return false;
+        }
+
+        if (mTimezone2 == null) {
+            if (other.mTimezone2 != null) {
+                return false;
+            }
+        } else if (!mTimezone2.equals(other.mTimezone2)) {
+            return false;
+        }
+
+        if (mTitle == null) {
+            if (other.mTitle != null) {
+                return false;
+            }
+        } else if (!mTitle.equals(other.mTitle)) {
+            return false;
+        }
+
+        if (mTransparency != other.mTransparency) {
+            return false;
+        }
+
+        if (mUri == null) {
+            if (other.mUri != null) {
+                return false;
+            }
+        } else if (!mUri.equals(other.mUri)) {
+            return false;
+        }
+
+        if (mVisibility != other.mVisibility) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/com/android/calendar/CalendarPreferenceActivity.java b/src/com/android/calendar/CalendarPreferenceActivity.java
deleted file mode 100644
index f4ad821..0000000
--- a/src/com/android/calendar/CalendarPreferenceActivity.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.preference.RingtonePreference;
-import android.provider.Calendar.CalendarCache;
-import android.text.TextUtils;
-
-public class CalendarPreferenceActivity extends PreferenceActivity implements
-        OnSharedPreferenceChangeListener, OnPreferenceChangeListener {
-    private static final String BUILD_VERSION = "build_version";
-
-    // The name of the shared preferences file. This name must be maintained for historical
-    // reasons, as it's what PreferenceManager assigned the first time the file was created.
-    private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
-
-    // Preference keys
-    static final String KEY_HIDE_DECLINED = "preferences_hide_declined";
-    static final String KEY_ALERTS_TYPE = "preferences_alerts_type";
-    static final String KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate";
-    static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen";
-    static final String KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone";
-    static final String KEY_DEFAULT_REMINDER = "preferences_default_reminder";
-    static final String KEY_START_VIEW = "startView";
-    static final String KEY_DETAILED_VIEW = "preferredDetailedView";
-    static final String KEY_DEFAULT_CALENDAR = "preference_defaultCalendar";
-    static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
-    static final String KEY_HOME_TZ = "preferences_home_tz";
-
-    // These must be in sync with the array preferences_alert_type_values
-    static final String ALERT_TYPE_ALERTS = "0";
-    static final String ALERT_TYPE_STATUS_BAR = "1";
-    static final String ALERT_TYPE_OFF = "2";
-
-    // Default preference values
-    static final String DEFAULT_START_VIEW =
-            CalendarApplication.ACTIVITY_NAMES[CalendarApplication.MONTH_VIEW_ID];
-    static final String DEFAULT_DETAILED_VIEW =
-            CalendarApplication.ACTIVITY_NAMES[CalendarApplication.DAY_VIEW_ID];
-
-
-    ListPreference mAlertType;
-    ListPreference mVibrateWhen;
-    RingtonePreference mRingtone;
-    CheckBoxPreference mUseHomeTZ;
-    ListPreference mHomeTZ;
-
-    private static CharSequence[][] mTimezones;
-
-    // In case we need to update something later
-    private Runnable mTZUpdater = null;
-
-    /** Return a properly configured SharedPreferences instance */
-    public static SharedPreferences getSharedPreferences(Context context) {
-        return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
-    }
-
-    /** Set the default shared preferences in the proper context */
-    public static void setDefaultValues(Context context) {
-        PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
-                R.xml.preferences, false);
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Make sure to always use the same preferences file regardless of the package name
-        // we're running under
-        PreferenceManager preferenceManager = getPreferenceManager();
-        SharedPreferences sharedPreferences = getSharedPreferences(this);
-        preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME);
-
-        // Load the preferences from an XML resource
-        addPreferencesFromResource(R.xml.preferences);
-
-        PreferenceScreen preferenceScreen = getPreferenceScreen();
-        preferenceScreen.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
-        mAlertType = (ListPreference) preferenceScreen.findPreference(KEY_ALERTS_TYPE);
-        mVibrateWhen = (ListPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE_WHEN);
-        mRingtone = (RingtonePreference) preferenceScreen.findPreference(KEY_ALERTS_RINGTONE);
-        mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED);
-        mUseHomeTZ.setOnPreferenceChangeListener(this);
-        mHomeTZ = (ListPreference) preferenceScreen.findPreference(KEY_HOME_TZ);
-        String tz = mHomeTZ.getValue();
-
-        if (mTimezones == null) {
-            mTimezones = (new TimezoneAdapter(this, tz)).getAllTimezones();
-        }
-        mHomeTZ.setEntryValues(mTimezones[0]);
-        mHomeTZ.setEntries(mTimezones[1]);
-        CharSequence tzName = mHomeTZ.getEntry();
-        if (!TextUtils.isEmpty(tzName)) {
-            mHomeTZ.setSummary(tzName);
-        } else {
-            mHomeTZ.setSummary(Utils.getTimeZone(this, mTZUpdater));
-        }
-        mHomeTZ.setOnPreferenceChangeListener(this);
-
-        // If needed, migrate vibration setting from a previous version
-        if (!sharedPreferences.contains(KEY_ALERTS_VIBRATE_WHEN) &&
-                sharedPreferences.contains(KEY_ALERTS_VIBRATE)) {
-            int stringId = sharedPreferences.getBoolean(KEY_ALERTS_VIBRATE, false) ?
-                    R.string.prefDefault_alerts_vibrate_true :
-                    R.string.prefDefault_alerts_vibrate_false;
-            mVibrateWhen.setValue(getString(stringId));
-        }
-
-        try {
-            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
-            findPreference(BUILD_VERSION).setSummary(packageInfo.versionName);
-        } catch (NameNotFoundException e) {
-            findPreference(BUILD_VERSION).setSummary("?");
-        }
-        updateChildPreferences();
-    }
-
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        if (key.equals(KEY_ALERTS_TYPE)) {
-            updateChildPreferences();
-        }
-    }
-
-    private void updateChildPreferences() {
-        if (mAlertType.getValue().equals(ALERT_TYPE_OFF)) {
-            mVibrateWhen.setValue(getString(R.string.prefDefault_alerts_vibrate_false));
-            mVibrateWhen.setEnabled(false);
-            mRingtone.setEnabled(false);
-        } else {
-            mVibrateWhen.setEnabled(true);
-            mRingtone.setEnabled(true);
-        }
-    }
-
-    /**
-     * Handles time zone preference changes
-     */
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        String tz;
-        if (preference == mUseHomeTZ) {
-            if ((Boolean)newValue) {
-                tz = mHomeTZ.getValue();
-            } else {
-                tz = CalendarCache.TIMEZONE_TYPE_AUTO;
-            }
-        } else if (preference == mHomeTZ) {
-            tz = (String)newValue;
-            mHomeTZ.setValue(tz);
-            mHomeTZ.setSummary(mHomeTZ.getEntry());
-        } else {
-            return false;
-        }
-        Utils.setTimeZone(this, tz);
-        return true;
-    }
-}
diff --git a/src/com/android/calendar/CalendarRecentSuggestionsProvider.java b/src/com/android/calendar/CalendarRecentSuggestionsProvider.java
new file mode 100644
index 0000000..deb66d8
--- /dev/null
+++ b/src/com/android/calendar/CalendarRecentSuggestionsProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import android.content.SearchRecentSuggestionsProvider;
+
+public class CalendarRecentSuggestionsProvider extends SearchRecentSuggestionsProvider {
+
+    public final static String AUTHORITY = "com.android.calendar.CalendarRecentSuggestionsProvider";
+
+    public final static int MODE = DATABASE_MODE_QUERIES;
+
+    public CalendarRecentSuggestionsProvider() {
+        setupSuggestions(AUTHORITY, MODE);
+    }
+
+}
diff --git a/src/com/android/calendar/CalendarSettingsActivity.java b/src/com/android/calendar/CalendarSettingsActivity.java
new file mode 100644
index 0000000..24ad293
--- /dev/null
+++ b/src/com/android/calendar/CalendarSettingsActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.provider.Calendar;
+import android.provider.Calendar.Calendars;
+
+import java.util.List;
+
+public class CalendarSettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.calendar_settings_headers, target);
+        Account[] accounts = AccountManager.get(this).getAccounts();
+        if (accounts != null) {
+            int length = accounts.length;
+            for (int i = 0; i < length; i++) {
+                Account acct = accounts[i];
+                if (ContentResolver.getIsSyncable(acct, Calendar.AUTHORITY) > 0) {
+                    Header accountHeader = new Header();
+                    accountHeader.title = acct.name;
+                    accountHeader.fragment =
+                            "com.android.calendar.selectcalendars.SelectCalendarsSyncFragment";
+                    Bundle args = new Bundle();
+                    args.putString(Calendars.ACCOUNT_NAME, acct.name);
+                    args.putString(Calendars.ACCOUNT_TYPE, acct.type);
+                    accountHeader.fragmentArguments = args;
+                    target.add(1, accountHeader);
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/calendar/CalendarView.java b/src/com/android/calendar/CalendarView.java
deleted file mode 100644
index 213f8c5..0000000
--- a/src/com/android/calendar/CalendarView.java
+++ /dev/null
@@ -1,3439 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import static android.provider.Calendar.EVENT_END_TIME;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Calendar.Attendees;
-import android.provider.Calendar.Calendars;
-import android.provider.Calendar.Events;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ImageView;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * This is the base class for a set of classes that implement views (day view
- * and week view to start with) that share some common code.
-  */
-public class CalendarView extends View
-        implements View.OnCreateContextMenuListener, View.OnClickListener {
-
-    private static float mScale = 0; // Used for supporting different screen densities
-    private static final long INVALID_EVENT_ID = -1; //This is used for remembering a null event
-
-    private boolean mOnFlingCalled;
-    /**
-     * ID of the last event which was displayed with the toast popup.
-     *
-     * This is used to prevent popping up multiple quick views for the same event, especially
-     * during calendar syncs. This becomes valid when an event is selected, either by default
-     * on starting calendar or by scrolling to an event. It becomes invalid when the user
-     * explicitly scrolls to an empty time slot, changes views, or deletes the event.
-     */
-    private long mLastPopupEventID;
-
-    protected CalendarApplication mCalendarApp;
-    protected CalendarActivity mParentActivity;
-
-    // This runs when we need to update the tz
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            String tz = Utils.getTimeZone(mContext, this);
-            // BaseDate we want to keep on the same day, so we swap tz
-            mBaseDate.timezone = tz;
-            mBaseDate.normalize(true);
-            // CurrentTime we want to keep at the same absolute time, so we
-            // call switch tz
-            mCurrentTime.switchTimezone(tz);
-            mTimeZone = TimeZone.getTimeZone(tz);
-            recalc();
-            mTitleTextView.setText(mDateRange);
-        }
-    };
-    private Context mContext;
-
-    private static final String[] CALENDARS_PROJECTION = new String[] {
-        Calendars._ID,          // 0
-        Calendars.ACCESS_LEVEL, // 1
-        Calendars.OWNER_ACCOUNT, // 2
-    };
-    private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
-    private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
-    private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
-
-    private static final String[] ATTENDEES_PROJECTION = new String[] {
-        Attendees._ID,                      // 0
-        Attendees.ATTENDEE_RELATIONSHIP,    // 1
-    };
-    private static final int ATTENDEES_INDEX_RELATIONSHIP = 1;
-    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
-
-    private static float SMALL_ROUND_RADIUS = 3.0F;
-
-    private static final int FROM_NONE = 0;
-    private static final int FROM_ABOVE = 1;
-    private static final int FROM_BELOW = 2;
-    private static final int FROM_LEFT = 4;
-    private static final int FROM_RIGHT = 8;
-
-    private static final int ACCESS_LEVEL_NONE = 0;
-    private static final int ACCESS_LEVEL_DELETE = 1;
-    private static final int ACCESS_LEVEL_EDIT = 2;
-
-    private static int HORIZONTAL_SCROLL_THRESHOLD = 50;
-
-    private ContinueScroll mContinueScroll = new ContinueScroll();
-
-    static private class DayHeader{
-        int cell;
-        String dateString;
-    }
-
-    private DayHeader[] dayHeaders = new DayHeader[32];
-
-    // Make this visible within the package for more informative debugging
-    Time mBaseDate;
-    private Time mCurrentTime;
-    //Update the current time line every five minutes if the window is left open that long
-    private static final int UPDATE_CURRENT_TIME_DELAY = 300000;
-    private UpdateCurrentTime mUpdateCurrentTime = new UpdateCurrentTime();
-    private int mTodayJulianDay;
-
-    private Typeface mBold = Typeface.DEFAULT_BOLD;
-    private int mFirstJulianDay;
-    private int mLastJulianDay;
-
-    private int mMonthLength;
-    private int mFirstDate;
-    private int[] mEarliestStartHour;    // indexed by the week day offset
-    private boolean[] mHasAllDayEvent;   // indexed by the week day offset
-
-    private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
-
-    /**
-     * This variable helps to avoid unnecessarily reloading events by keeping
-     * track of the start millis parameter used for the most recent loading
-     * of events.  If the next reload matches this, then the events are not
-     * reloaded.  To force a reload, set this to zero (this is set to zero
-     * in the method clearCachedEvents()).
-     */
-    private long mLastReloadMillis;
-
-    private ArrayList<Event> mEvents = new ArrayList<Event>();
-    private int mSelectionDay;        // Julian day
-    private int mSelectionHour;
-
-    /* package private so that CalendarActivity can read it when creating new
-     * events
-     */
-    boolean mSelectionAllDay;
-
-    private int mCellWidth;
-
-    // Pre-allocate these objects and re-use them
-    private Rect mRect = new Rect();
-    private RectF mRectF = new RectF();
-    private Rect mSrcRect = new Rect();
-    private Rect mDestRect = new Rect();
-    private Paint mPaint = new Paint();
-    private Paint mPaintBorder = new Paint();
-    private Paint mEventTextPaint = new Paint();
-    private Paint mSelectionPaint = new Paint();
-    private Path mPath = new Path();
-
-    protected boolean mDrawTextInEventRect;
-    private int mStartDay;
-
-    private PopupWindow mPopup;
-    private View mPopupView;
-
-    // The number of milliseconds to show the popup window
-    private static final int POPUP_DISMISS_DELAY = 3000;
-    private DismissPopup mDismissPopup = new DismissPopup();
-
-    // For drawing to an off-screen Canvas
-    private Bitmap mBitmap;
-    private Canvas mCanvas;
-    private boolean mRedrawScreen = true;
-    private boolean mRemeasure = true;
-
-    private final EventLoader mEventLoader;
-    protected final EventGeometry mEventGeometry;
-
-    private static final int DAY_GAP = 1;
-    private static final int HOUR_GAP = 1;
-    private static int SINGLE_ALLDAY_HEIGHT = 20;
-    private static int MAX_ALLDAY_HEIGHT = 72;
-    private static int ALLDAY_TOP_MARGIN = 3;
-    private static int MAX_ALLDAY_EVENT_HEIGHT = 18;
-
-    /* The extra space to leave above the text in all-day events */
-    private static final int ALL_DAY_TEXT_TOP_MARGIN = 0;
-
-    /* The extra space to leave above the text in normal events */
-    private static final int NORMAL_TEXT_TOP_MARGIN = 2;
-
-    private static final int HOURS_LEFT_MARGIN = 2;
-    private static final int HOURS_RIGHT_MARGIN = 4;
-    private static final int HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
-
-    private static int CURRENT_TIME_LINE_HEIGHT = 2;
-    private static int CURRENT_TIME_LINE_BORDER_WIDTH = 1;
-    private static int CURRENT_TIME_MARKER_INNER_WIDTH = 6;
-    private static int CURRENT_TIME_MARKER_HEIGHT = 6;
-    private static int CURRENT_TIME_MARKER_WIDTH = 8;
-    private static int CURRENT_TIME_LINE_SIDE_BUFFER = 1;
-
-    /* package */ static final int MINUTES_PER_HOUR = 60;
-    /* package */ static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
-    /* package */ static final int MILLIS_PER_MINUTE = 60 * 1000;
-    /* package */ static final int MILLIS_PER_HOUR = (3600 * 1000);
-    /* package */ static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
-
-    private static int NORMAL_FONT_SIZE = 12;
-    private static int EVENT_TEXT_FONT_SIZE = 12;
-    private static int HOURS_FONT_SIZE = 12;
-    private static int AMPM_FONT_SIZE = 9;
-    private static int MIN_CELL_WIDTH_FOR_TEXT = 27;
-    private static final int MAX_EVENT_TEXT_LEN = 500;
-    private static float MIN_EVENT_HEIGHT = 15.0F;  // in pixels
-
-    // This value forces the position calculator to take care of the overwap which can't be
-    // detected from the view of event time but actually is detected when rendering them.
-    //
-    // Detail:
-    // Imagine there are two events: A (from 1:00pm to 1:01pm) and B (from 1:02pm to 2:00pm).
-    // The position calculator (Event#doComputePositions()), marks them as "not overwrapped"
-    // as A finishes before B's begin time, so those events are put on the same column
-    // (or, horizontal position).
-    // From the view of renderer, however, the actual rectangle for A is larger than "1 min."
-    // for accomodating at least 1 line of text in it.
-    // As a result, A's rectangle is overwrapped by B's, and A becomes hard to be touched
-    // without trackball or DPAD (as, it is beneath B from the user' view).
-    // This values forces the original calculator to take care of the actual overwrap detected in
-    // rendering time.
-    //
-    // Note:
-    // Theoretically we can calcurate an ideal value for this purpose by making the calculator
-    // understand the relation between each event and pixel-level height of actual rectangles,
-    // but we don't do so as currently the calculator doesn't have convenient way to obtain
-    // necessary values for the calculation.
-    /* package */ static int EVENT_OVERWRAP_MARGIN_TIME = MILLIS_PER_MINUTE * 15;
-
-    private static int mSelectionColor;
-    private static int mPressedColor;
-    private static int mSelectedEventTextColor;
-    private static int mEventTextColor;
-    private static int mWeek_saturdayColor;
-    private static int mWeek_sundayColor;
-    private static int mCalendarDateBannerTextColor;
-    private static int mCalendarAllDayBackground;
-    private static int mCalendarAmPmLabel;
-    private static int mCalendarDateBannerBackground;
-    private static int mCalendarDateSelected;
-    private static int mCalendarGridAreaBackground;
-    private static int mCalendarGridAreaSelected;
-    private static int mCalendarGridLineHorizontalColor;
-    private static int mCalendarGridLineVerticalColor;
-    private static int mCalendarHourBackground;
-    private static int mCalendarHourLabel;
-    private static int mCalendarHourSelected;
-    private static int mCurrentTimeMarkerColor;
-    private static int mCurrentTimeLineColor;
-    private static int mCurrentTimeMarkerBorderColor;
-
-    private int mViewStartX;
-    private int mViewStartY;
-    private int mMaxViewStartY;
-    private int mBitmapHeight;
-    private int mViewHeight;
-    private int mViewWidth;
-    private int mGridAreaHeight;
-    private int mCellHeight;
-    private int mScrollStartY;
-    private int mPreviousDirection;
-    private int mPreviousDistanceX;
-
-    private int mHoursTextHeight;
-    private int mEventTextAscent;
-    private int mEventTextHeight;
-    private int mAllDayHeight;
-    private int mBannerPlusMargin;
-    private int mMaxAllDayEvents;
-
-    protected int mNumDays = 7;
-    private int mNumHours = 10;
-    private int mHoursWidth;
-    private int mDateStrWidth;
-    private int mFirstCell;
-    private int mFirstHour = -1;
-    private int mFirstHourOffset;
-    private String[] mHourStrs;
-    private String[] mDayStrs;
-    private String[] mDayStrs2Letter;
-    private boolean mIs24HourFormat;
-
-    private float[] mCharWidths = new float[MAX_EVENT_TEXT_LEN];
-    private ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
-    private boolean mComputeSelectedEvents;
-    private Event mSelectedEvent;
-    private Event mPrevSelectedEvent;
-    private Rect mPrevBox = new Rect();
-    protected final Resources mResources;
-    private String mAmString;
-    private String mPmString;
-    private DeleteEventHelper mDeleteEventHelper;
-
-    private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
-
-    /**
-     * The initial state of the touch mode when we enter this view.
-     */
-    private static final int TOUCH_MODE_INITIAL_STATE = 0;
-
-    /**
-     * Indicates we just received the touch event and we are waiting to see if
-     * it is a tap or a scroll gesture.
-     */
-    private static final int TOUCH_MODE_DOWN = 1;
-
-    /**
-     * Indicates the touch gesture is a vertical scroll
-     */
-    private static final int TOUCH_MODE_VSCROLL = 0x20;
-
-    /**
-     * Indicates the touch gesture is a horizontal scroll
-     */
-    private static final int TOUCH_MODE_HSCROLL = 0x40;
-
-    private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
-    /**
-     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
-     */
-    private static final int SELECTION_HIDDEN = 0;
-    private static final int SELECTION_PRESSED = 1;
-    private static final int SELECTION_SELECTED = 2;
-    private static final int SELECTION_LONGPRESS = 3;
-
-    private int mSelectionMode = SELECTION_HIDDEN;
-
-    private boolean mScrolling = false;
-
-    private TimeZone mTimeZone;
-    private String mDateRange;
-    private TextView mTitleTextView;
-
-    // Accessibility support related members
-
-    private int mPrevSelectionDay;
-    private int mPrevSelectionHour;
-    private CharSequence mPrevTitleTextViewText;
-    private Bundle mTempEventBundle;
-
-    public CalendarView(CalendarActivity activity) {
-        super(activity);
-        if (mScale == 0) {
-            mScale = getContext().getResources().getDisplayMetrics().density;
-            if (mScale != 1) {
-                SINGLE_ALLDAY_HEIGHT *= mScale;
-                MAX_ALLDAY_HEIGHT *= mScale;
-                ALLDAY_TOP_MARGIN *= mScale;
-                MAX_ALLDAY_EVENT_HEIGHT *= mScale;
-
-                NORMAL_FONT_SIZE *= mScale;
-                EVENT_TEXT_FONT_SIZE *= mScale;
-                HOURS_FONT_SIZE *= mScale;
-                AMPM_FONT_SIZE *= mScale;
-                MIN_CELL_WIDTH_FOR_TEXT *= mScale;
-                MIN_EVENT_HEIGHT *= mScale;
-
-                HORIZONTAL_SCROLL_THRESHOLD *= mScale;
-
-                CURRENT_TIME_MARKER_HEIGHT *= mScale;
-                CURRENT_TIME_MARKER_WIDTH *= mScale;
-                CURRENT_TIME_LINE_HEIGHT *= mScale;
-                CURRENT_TIME_LINE_BORDER_WIDTH *= mScale;
-                CURRENT_TIME_MARKER_INNER_WIDTH *= mScale;
-                CURRENT_TIME_LINE_SIDE_BUFFER *= mScale;
-
-                SMALL_ROUND_RADIUS *= mScale;
-            }
-        }
-
-        mResources = activity.getResources();
-        mEventLoader = activity.mEventLoader;
-        mEventGeometry = new EventGeometry();
-        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
-        mEventGeometry.setHourGap(HOUR_GAP);
-        mParentActivity = activity;
-        mCalendarApp = (CalendarApplication) mParentActivity.getApplication();
-        mDeleteEventHelper = new DeleteEventHelper(activity, false /* don't exit when done */);
-        mLastPopupEventID = INVALID_EVENT_ID;
-
-        init(activity);
-    }
-
-    private void init(Context context) {
-        setFocusable(true);
-
-        // Allow focus in touch mode so that we can do keyboard shortcuts
-        // even after we've entered touch mode.
-        setFocusableInTouchMode(true);
-        setClickable(true);
-        setOnCreateContextMenuListener(this);
-
-        mStartDay = Utils.getFirstDayOfWeek();
-
-        mTimeZone = TimeZone.getTimeZone(Utils.getTimeZone(context, mUpdateTZ));
-
-        mContext = context;
-        mCurrentTime = new Time(Utils.getTimeZone(context, mUpdateTZ));
-        long currentTime = System.currentTimeMillis();
-        mCurrentTime.set(currentTime);
-        //The % makes it go off at the next increment of 5 minutes.
-        postDelayed(mUpdateCurrentTime,
-                UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
-        mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
-
-        mWeek_saturdayColor = mResources.getColor(R.color.week_saturday);
-        mWeek_sundayColor = mResources.getColor(R.color.week_sunday);
-        mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color);
-        mCalendarAllDayBackground = mResources.getColor(R.color.calendar_all_day_background);
-        mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label);
-        mCalendarDateBannerBackground = mResources.getColor(R.color.calendar_date_banner_background);
-        mCalendarDateSelected = mResources.getColor(R.color.calendar_date_selected);
-        mCalendarGridAreaBackground = mResources.getColor(R.color.calendar_grid_area_background);
-        mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected);
-        mCalendarGridLineHorizontalColor = mResources.getColor(R.color.calendar_grid_line_horizontal_color);
-        mCalendarGridLineVerticalColor = mResources.getColor(R.color.calendar_grid_line_vertical_color);
-        mCalendarHourBackground = mResources.getColor(R.color.calendar_hour_background);
-        mCalendarHourLabel = mResources.getColor(R.color.calendar_hour_label);
-        mCalendarHourSelected = mResources.getColor(R.color.calendar_hour_selected);
-        mSelectionColor = mResources.getColor(R.color.selection);
-        mPressedColor = mResources.getColor(R.color.pressed);
-        mSelectedEventTextColor = mResources.getColor(R.color.calendar_event_selected_text_color);
-        mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
-        mCurrentTimeMarkerColor = mResources.getColor(R.color.current_time_marker);
-        mCurrentTimeLineColor = mResources.getColor(R.color.current_time_line);
-        mCurrentTimeMarkerBorderColor = mResources.getColor(R.color.current_time_marker_border);
-        mEventTextPaint.setColor(mEventTextColor);
-        mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
-        mEventTextPaint.setTextAlign(Paint.Align.LEFT);
-        mEventTextPaint.setAntiAlias(true);
-
-        int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
-        Paint p = mSelectionPaint;
-        p.setColor(gridLineColor);
-        p.setStyle(Style.STROKE);
-        p.setStrokeWidth(2.0f);
-        p.setAntiAlias(false);
-
-        p = mPaint;
-        p.setAntiAlias(true);
-
-        mPaintBorder.setColor(0xffc8c8c8);
-        mPaintBorder.setStyle(Style.STROKE);
-        mPaintBorder.setAntiAlias(true);
-        mPaintBorder.setStrokeWidth(2.0f);
-
-        // Allocate space for 2 weeks worth of weekday names so that we can
-        // easily start the week display at any week day.
-        mDayStrs = new String[14];
-
-        // Also create an array of 2-letter abbreviations.
-        mDayStrs2Letter = new String[14];
-
-        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
-            int index = i - Calendar.SUNDAY;
-            // e.g. Tue for Tuesday
-            mDayStrs[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM);
-            mDayStrs[index + 7] = mDayStrs[index];
-            // e.g. Tu for Tuesday
-            mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT);
-
-            // If we don't have 2-letter day strings, fall back to 1-letter.
-            if (mDayStrs2Letter[index].equals(mDayStrs[index])) {
-                mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORTEST);
-            }
-
-            mDayStrs2Letter[index + 7] = mDayStrs2Letter[index];
-        }
-
-        // Figure out how much space we need for the 3-letter abbrev names
-        // in the worst case.
-        p.setTextSize(NORMAL_FONT_SIZE);
-        p.setTypeface(mBold);
-        String[] dateStrs = {" 28", " 30"};
-        mDateStrWidth = computeMaxStringWidth(0, dateStrs, p);
-        mDateStrWidth += computeMaxStringWidth(0, mDayStrs, p);
-
-        p.setTextSize(HOURS_FONT_SIZE);
-        p.setTypeface(null);
-        updateIs24HourFormat();
-
-        mAmString = DateUtils.getAMPMString(Calendar.AM);
-        mPmString = DateUtils.getAMPMString(Calendar.PM);
-        String[] ampm = {mAmString, mPmString};
-        p.setTextSize(AMPM_FONT_SIZE);
-        mHoursWidth = computeMaxStringWidth(mHoursWidth, ampm, p);
-        mHoursWidth += HOURS_MARGIN;
-
-        LayoutInflater inflater;
-        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mPopupView = inflater.inflate(R.layout.bubble_event, null);
-        mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT));
-        mPopup = new PopupWindow(context);
-        mPopup.setContentView(mPopupView);
-        Resources.Theme dialogTheme = getResources().newTheme();
-        dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
-        TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
-            android.R.attr.windowBackground });
-        mPopup.setBackgroundDrawable(ta.getDrawable(0));
-        ta.recycle();
-
-        // Enable touching the popup window
-        mPopupView.setOnClickListener(this);
-
-        mBaseDate = new Time(Utils.getTimeZone(context, mUpdateTZ));
-        long millis = System.currentTimeMillis();
-        mBaseDate.set(millis);
-
-        mEarliestStartHour = new int[mNumDays];
-        mHasAllDayEvent = new boolean[mNumDays];
-
-        mNumHours = context.getResources().getInteger(R.integer.number_of_hours);
-        mTitleTextView = (TextView) mParentActivity.findViewById(R.id.title);
-    }
-
-    /**
-     * This is called when the popup window is pressed.
-     */
-    public void onClick(View v) {
-        if (v == mPopupView) {
-            // Pretend it was a trackball click because that will always
-            // jump to the "View event" screen.
-            switchViews(true /* trackball */);
-        }
-    }
-
-    public void updateIs24HourFormat() {
-        mIs24HourFormat = DateFormat.is24HourFormat(mParentActivity);
-        mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
-    }
-
-    /**
-     * Returns the start of the selected time in milliseconds since the epoch.
-     *
-     * @return selected time in UTC milliseconds since the epoch.
-     */
-    long getSelectedTimeInMillis() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        return time.normalize(true /* ignore isDst */);
-    }
-
-    Time getSelectedTime() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        time.normalize(true /* ignore isDst */);
-        return time;
-    }
-
-    /**
-     * Returns the start of the selected time in minutes since midnight,
-     * local time.  The derived class must ensure that this is consistent
-     * with the return value from getSelectedTimeInMillis().
-     */
-    int getSelectedMinutesSinceMidnight() {
-        return mSelectionHour * MINUTES_PER_HOUR;
-    }
-
-    public void setSelectedDay(Time time) {
-        mBaseDate.set(time);
-        mSelectionHour = mBaseDate.hour;
-        mSelectedEvent = null;
-        mPrevSelectedEvent = null;
-        long millis = mBaseDate.toMillis(false /* use isDst */);
-        mSelectionDay = Time.getJulianDay(millis, mBaseDate.gmtoff);
-        mSelectedEvents.clear();
-        mComputeSelectedEvents = true;
-
-        // Force a recalculation of the first visible hour
-        mFirstHour = -1;
-        recalc();
-        mTitleTextView.setText(mDateRange);
-
-        // Force a redraw of the selection box.
-        mSelectionMode = SELECTION_SELECTED;
-        mRedrawScreen = true;
-        mRemeasure = true;
-        invalidate();
-    }
-
-    public Time getSelectedDay() {
-        Time time = new Time(mBaseDate);
-        time.setJulianDay(mSelectionDay);
-        time.hour = mSelectionHour;
-
-        // We ignore the "isDst" field because we want normalize() to figure
-        // out the correct DST value and not adjust the selected time based
-        // on the current setting of DST.
-        time.normalize(true /* ignore isDst */);
-        return time;
-    }
-
-    private void recalc() {
-        // Set the base date to the beginning of the week if we are displaying
-        // 7 days at a time.
-        if (mNumDays == 7) {
-            int dayOfWeek = mBaseDate.weekDay;
-            int diff = dayOfWeek - mStartDay;
-            if (diff != 0) {
-                if (diff < 0) {
-                    diff += 7;
-                }
-                mBaseDate.monthDay -= diff;
-                mBaseDate.normalize(true /* ignore isDst */);
-            }
-        }
-
-        long start = mBaseDate.normalize(true /* use isDst */);
-        long end = start;
-        mFirstJulianDay = Time.getJulianDay(start, mBaseDate.gmtoff);
-        mLastJulianDay = mFirstJulianDay + mNumDays - 1;
-
-        mMonthLength = mBaseDate.getActualMaximum(Time.MONTH_DAY);
-        mFirstDate = mBaseDate.monthDay;
-
-        int flags = DateUtils.FORMAT_SHOW_YEAR;
-        if (DateFormat.is24HourFormat(mParentActivity)) {
-            flags |= DateUtils.FORMAT_24HOUR;
-        }
-        if (mNumDays > 1) {
-            mBaseDate.monthDay += mNumDays - 1;
-            end = mBaseDate.toMillis(true /* ignore isDst */);
-            mBaseDate.monthDay -= mNumDays - 1;
-            flags |= DateUtils.FORMAT_NO_MONTH_DAY;
-        } else {
-            flags |= DateUtils.FORMAT_SHOW_WEEKDAY
-                    | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
-        }
-
-        mDateRange = Utils.formatDateRange(mParentActivity, start, end, flags);
-
-        if (!TextUtils.equals(Utils.getTimeZone(mContext, mUpdateTZ), Time.getCurrentTimezone())) {
-            flags = DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(mParentActivity)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            start = System.currentTimeMillis();
-
-            String tz = Utils.getTimeZone(mContext, mUpdateTZ);
-            boolean isDST = mBaseDate.isDst != 0;
-            StringBuilder title = new StringBuilder(mDateRange);
-            title.append(" (").append(Utils.formatDateRange(mContext, start, start, flags))
-                    .append(" ")
-                    .append(mTimeZone.getDisplayName(isDST, TimeZone.SHORT, Locale.getDefault()))
-                    .append(")");
-            mDateRange = title.toString();
-        }
-        // Do not set the title here because this is called when executing
-        // initNextView() to prepare the Day view when sliding the finger
-        // horizontally but we don't always want to change the title.  And
-        // if we change the title here and then change it back in the caller
-        // then we get an annoying flicker.
-    }
-
-    void setDetailedView(String detailedView) {
-        mDetailedView = detailedView;
-    }
-
-    @Override
-    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
-        mViewWidth = width;
-        mViewHeight = height;
-        int gridAreaWidth = width - mHoursWidth;
-        mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
-
-        Paint p = new Paint();
-        p.setTextSize(NORMAL_FONT_SIZE);
-        int bannerTextHeight = (int) Math.abs(p.ascent());
-
-        p.setTextSize(HOURS_FONT_SIZE);
-        mHoursTextHeight = (int) Math.abs(p.ascent());
-
-        p.setTextSize(EVENT_TEXT_FONT_SIZE);
-        float ascent = -p.ascent();
-        mEventTextAscent = (int) Math.ceil(ascent);
-        float totalHeight = ascent + p.descent();
-        mEventTextHeight = (int) Math.ceil(totalHeight);
-
-        if (mNumDays > 1) {
-            mBannerPlusMargin = bannerTextHeight + 14;
-        } else {
-            mBannerPlusMargin = 0;
-        }
-
-        remeasure(width, height);
-    }
-
-    // Measures the space needed for various parts of the view after
-    // loading new events.  This can change if there are all-day events.
-    private void remeasure(int width, int height) {
-
-        // First, clear the array of earliest start times, and the array
-        // indicating presence of an all-day event.
-        for (int day = 0; day < mNumDays; day++) {
-            mEarliestStartHour[day] = 25;  // some big number
-            mHasAllDayEvent[day] = false;
-        }
-
-        // Compute the space needed for the all-day events, if any.
-        // Make a pass over all the events, and keep track of the maximum
-        // number of all-day events in any one day.  Also, keep track of
-        // the earliest event in each day.
-        int maxAllDayEvents = 0;
-        ArrayList<Event> events = mEvents;
-        int len = events.size();
-        for (int ii = 0; ii < len; ii++) {
-            Event event = events.get(ii);
-            if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay)
-                continue;
-            if (event.allDay) {
-                int max = event.getColumn() + 1;
-                if (maxAllDayEvents < max) {
-                    maxAllDayEvents = max;
-                }
-                int daynum = event.startDay - mFirstJulianDay;
-                int durationDays = event.endDay - event.startDay + 1;
-                if (daynum < 0) {
-                    durationDays += daynum;
-                    daynum = 0;
-                }
-                if (daynum + durationDays > mNumDays) {
-                    durationDays = mNumDays - daynum;
-                }
-                for (int day = daynum; durationDays > 0; day++, durationDays--) {
-                    mHasAllDayEvent[day] = true;
-                }
-            } else {
-                int daynum = event.startDay - mFirstJulianDay;
-                int hour = event.startTime / 60;
-                if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
-                    mEarliestStartHour[daynum] = hour;
-                }
-
-                // Also check the end hour in case the event spans more than
-                // one day.
-                daynum = event.endDay - mFirstJulianDay;
-                hour = event.endTime / 60;
-                if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
-                    mEarliestStartHour[daynum] = hour;
-                }
-            }
-        }
-        mMaxAllDayEvents = maxAllDayEvents;
-
-        mFirstCell = mBannerPlusMargin;
-        int allDayHeight = 0;
-        if (maxAllDayEvents > 0) {
-            // If there is at most one all-day event per day, then use less
-            // space (but more than the space for a single event).
-            if (maxAllDayEvents == 1) {
-                allDayHeight = SINGLE_ALLDAY_HEIGHT;
-            } else {
-                // Allow the all-day area to grow in height depending on the
-                // number of all-day events we need to show, up to a limit.
-                allDayHeight = maxAllDayEvents * MAX_ALLDAY_EVENT_HEIGHT;
-                if (allDayHeight > MAX_ALLDAY_HEIGHT) {
-                    allDayHeight = MAX_ALLDAY_HEIGHT;
-                }
-            }
-            mFirstCell = mBannerPlusMargin + allDayHeight + ALLDAY_TOP_MARGIN;
-        } else {
-            mSelectionAllDay = false;
-        }
-        mAllDayHeight = allDayHeight;
-
-        mGridAreaHeight = height - mFirstCell;
-        mCellHeight = (mGridAreaHeight - ((mNumHours + 1) * HOUR_GAP)) / mNumHours;
-        int usedGridAreaHeight = (mCellHeight + HOUR_GAP) * mNumHours + HOUR_GAP;
-        int bottomSpace = mGridAreaHeight - usedGridAreaHeight;
-        mEventGeometry.setHourHeight(mCellHeight);
-
-        // Create an off-screen bitmap that we can draw into.
-        mBitmapHeight = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) + bottomSpace;
-        if ((mBitmap == null || mBitmap.getHeight() < mBitmapHeight) && width > 0 &&
-                mBitmapHeight > 0) {
-            if (mBitmap != null) {
-                mBitmap.recycle();
-            }
-            mBitmap = Bitmap.createBitmap(width, mBitmapHeight, Bitmap.Config.RGB_565);
-            mCanvas = new Canvas(mBitmap);
-        }
-        mMaxViewStartY = mBitmapHeight - mGridAreaHeight;
-
-        if (mFirstHour == -1) {
-            initFirstHour();
-            mFirstHourOffset = 0;
-        }
-
-        // When we change the base date, the number of all-day events may
-        // change and that changes the cell height.  When we switch dates,
-        // we use the mFirstHourOffset from the previous view, but that may
-        // be too large for the new view if the cell height is smaller.
-        if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
-            mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
-        }
-        mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
-
-        int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
-        //When we get new events we don't want to dismiss the popup unless the event changes
-        if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent.id) {
-            mPopup.dismiss();
-        }
-        mPopup.setWidth(eventAreaWidth - 20);
-        mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
-    }
-
-    /**
-     * Initialize the state for another view.  The given view is one that has
-     * its own bitmap and will use an animation to replace the current view.
-     * The current view and new view are either both Week views or both Day
-     * views.  They differ in their base date.
-     *
-     * @param view the view to initialize.
-     */
-    private void initView(CalendarView view) {
-        view.mSelectionHour = mSelectionHour;
-        view.mSelectedEvents.clear();
-        view.mComputeSelectedEvents = true;
-        view.mFirstHour = mFirstHour;
-        view.mFirstHourOffset = mFirstHourOffset;
-        view.remeasure(getWidth(), getHeight());
-
-        view.mSelectedEvent = null;
-        view.mPrevSelectedEvent = null;
-        view.mStartDay = mStartDay;
-        if (view.mEvents.size() > 0) {
-            view.mSelectionAllDay = mSelectionAllDay;
-        } else {
-            view.mSelectionAllDay = false;
-        }
-
-        // Redraw the screen so that the selection box will be redrawn.  We may
-        // have scrolled to a different part of the day in some other view
-        // so the selection box in this view may no longer be visible.
-        view.mRedrawScreen = true;
-        view.recalc();
-    }
-
-    /**
-     * Switch to another view based on what was selected (an event or a free
-     * slot) and how it was selected (by touch or by trackball).
-     *
-     * @param trackBallSelection true if the selection was made using the
-     * trackball.
-     */
-    private void switchViews(boolean trackBallSelection) {
-        Event selectedEvent = mSelectedEvent;
-
-        mPopup.dismiss();
-        mLastPopupEventID = INVALID_EVENT_ID;
-        if (mNumDays > 1) {
-            // This is the Week view.
-            // With touch, we always switch to Day/Agenda View
-            // With track ball, if we selected a free slot, then create an event.
-            // If we selected a specific event, switch to EventInfo view.
-            if (trackBallSelection) {
-                if (selectedEvent == null) {
-                    // Switch to the EditEvent view
-                    long startMillis = getSelectedTimeInMillis();
-                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    intent.setClassName(mParentActivity, EditEvent.class.getName());
-                    intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-                    intent.putExtra(EVENT_END_TIME, endMillis);
-                    mParentActivity.startActivity(intent);
-                } else {
-                    // Switch to the EventInfo view
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI,
-                            selectedEvent.id);
-                    intent.setData(eventUri);
-                    intent.setClassName(mParentActivity, EventInfoActivity.class.getName());
-                    intent.putExtra(EVENT_BEGIN_TIME, selectedEvent.startMillis);
-                    intent.putExtra(EVENT_END_TIME, selectedEvent.endMillis);
-                    mParentActivity.startActivity(intent);
-                }
-            } else {
-                // This was a touch selection.  If the touch selected a single
-                // unambiguous event, then view that event.  Otherwise go to
-                // Day/Agenda view.
-                if (mSelectedEvents.size() == 1) {
-                    // Switch to the EventInfo view
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI,
-                            selectedEvent.id);
-                    intent.setData(eventUri);
-                    intent.setClassName(mParentActivity, EventInfoActivity.class.getName());
-                    intent.putExtra(EVENT_BEGIN_TIME, selectedEvent.startMillis);
-                    intent.putExtra(EVENT_END_TIME, selectedEvent.endMillis);
-                    mParentActivity.startActivity(intent);
-                } else {
-                    // Switch to the Day/Agenda view.
-                    long millis = getSelectedTimeInMillis();
-                    Utils.startActivity(mParentActivity, mDetailedView, millis);
-                }
-            }
-        } else {
-            // This is the Day view.
-            // If we selected a free slot, then create an event.
-            // If we selected an event, then go to the EventInfo view.
-            if (selectedEvent == null) {
-                // Switch to the EditEvent view
-                long startMillis = getSelectedTimeInMillis();
-                long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setClassName(mParentActivity, EditEvent.class.getName());
-                intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-                intent.putExtra(EVENT_END_TIME, endMillis);
-                mParentActivity.startActivity(intent);
-            } else {
-                // Switch to the EventInfo view
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, selectedEvent.id);
-                intent.setData(eventUri);
-                intent.setClassName(mParentActivity, EventInfoActivity.class.getName());
-                intent.putExtra(EVENT_BEGIN_TIME, selectedEvent.startMillis);
-                intent.putExtra(EVENT_END_TIME, selectedEvent.endMillis);
-                mParentActivity.startActivity(intent);
-            }
-        }
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        mScrolling = false;
-        long duration = event.getEventTime() - event.getDownTime();
-
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-                if (mSelectionMode == SELECTION_HIDDEN) {
-                    // Don't do anything unless the selection is visible.
-                    break;
-                }
-
-                if (mSelectionMode == SELECTION_PRESSED) {
-                    // This was the first press when there was nothing selected.
-                    // Change the selection from the "pressed" state to the
-                    // the "selected" state.  We treat short-press and
-                    // long-press the same here because nothing was selected.
-                    mSelectionMode = SELECTION_SELECTED;
-                    mRedrawScreen = true;
-                    invalidate();
-                    break;
-                }
-
-                // Check the duration to determine if this was a short press
-                if (duration < ViewConfiguration.getLongPressTimeout()) {
-                    switchViews(true /* trackball */);
-                } else {
-                    mSelectionMode = SELECTION_LONGPRESS;
-                    mRedrawScreen = true;
-                    invalidate();
-                    performLongClick();
-                }
-                break;
-            case KeyEvent.KEYCODE_BACK:
-                if (event.isTracking() && !event.isCanceled()) {
-                    mPopup.dismiss();
-                    mParentActivity.finish();
-                    return true;
-                }
-                break;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mSelectionMode == SELECTION_HIDDEN) {
-            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
-                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
-                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                // Display the selection box but don't move or select it
-                // on this key press.
-                mSelectionMode = SELECTION_SELECTED;
-                mRedrawScreen = true;
-                invalidate();
-                return true;
-            } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
-                // Display the selection box but don't select it
-                // on this key press.
-                mSelectionMode = SELECTION_PRESSED;
-                mRedrawScreen = true;
-                invalidate();
-                return true;
-            }
-        }
-
-        mSelectionMode = SELECTION_SELECTED;
-        mScrolling = false;
-        boolean redraw;
-        int selectionDay = mSelectionDay;
-
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_DEL:
-            // Delete the selected event, if any
-            Event selectedEvent = mSelectedEvent;
-            if (selectedEvent == null) {
-                return false;
-            }
-            mPopup.dismiss();
-            mLastPopupEventID = INVALID_EVENT_ID;
-
-            long begin = selectedEvent.startMillis;
-            long end = selectedEvent.endMillis;
-            long id = selectedEvent.id;
-            mDeleteEventHelper.delete(begin, end, id, -1);
-            return true;
-        case KeyEvent.KEYCODE_ENTER:
-            switchViews(true /* trackball or keyboard */);
-            return true;
-        case KeyEvent.KEYCODE_BACK:
-            if (event.getRepeatCount() == 0) {
-                event.startTracking();
-                return true;
-            }
-            return super.onKeyDown(keyCode, event);
-        case KeyEvent.KEYCODE_DPAD_LEFT:
-            if (mSelectedEvent != null) {
-                mSelectedEvent = mSelectedEvent.nextLeft;
-            }
-            if (mSelectedEvent == null) {
-                mLastPopupEventID = INVALID_EVENT_ID;
-                selectionDay -= 1;
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_RIGHT:
-            if (mSelectedEvent != null) {
-                mSelectedEvent = mSelectedEvent.nextRight;
-            }
-            if (mSelectedEvent == null) {
-                mLastPopupEventID = INVALID_EVENT_ID;
-                selectionDay += 1;
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_UP:
-            if (mSelectedEvent != null) {
-                mSelectedEvent = mSelectedEvent.nextUp;
-            }
-            if (mSelectedEvent == null) {
-                mLastPopupEventID = INVALID_EVENT_ID;
-                if (!mSelectionAllDay) {
-                    mSelectionHour -= 1;
-                    adjustHourSelection();
-                    mSelectedEvents.clear();
-                    mComputeSelectedEvents = true;
-                }
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_DOWN:
-            if (mSelectedEvent != null) {
-                mSelectedEvent = mSelectedEvent.nextDown;
-            }
-            if (mSelectedEvent == null) {
-                mLastPopupEventID = INVALID_EVENT_ID;
-                if (mSelectionAllDay) {
-                    mSelectionAllDay = false;
-                } else {
-                    mSelectionHour++;
-                    adjustHourSelection();
-                    mSelectedEvents.clear();
-                    mComputeSelectedEvents = true;
-                }
-            }
-            redraw = true;
-            break;
-
-        default:
-            return super.onKeyDown(keyCode, event);
-        }
-
-        if ((selectionDay < mFirstJulianDay) || (selectionDay > mLastJulianDay)) {
-            boolean forward;
-            CalendarView view = mParentActivity.getNextView();
-            Time date = view.mBaseDate;
-            date.set(mBaseDate);
-            if (selectionDay < mFirstJulianDay) {
-                date.monthDay -= mNumDays;
-                forward = false;
-            } else {
-                date.monthDay += mNumDays;
-                forward = true;
-            }
-            date.normalize(true /* ignore isDst */);
-            view.mSelectionDay = selectionDay;
-
-            initView(view);
-            mTitleTextView.setText(view.mDateRange);
-            mParentActivity.switchViews(forward, 0, 0);
-            return true;
-        }
-        mSelectionDay = selectionDay;
-        mSelectedEvents.clear();
-        mComputeSelectedEvents = true;
-
-        if (redraw) {
-            mRedrawScreen = true;
-            invalidate();
-            return true;
-        }
-
-        return super.onKeyDown(keyCode, event);
-    }
-
-    // This is called after scrolling stops to move the selected hour
-    // to the visible part of the screen.
-    private void resetSelectedHour() {
-        if (mSelectionHour < mFirstHour + 1) {
-            mSelectionHour = mFirstHour + 1;
-            mSelectedEvent = null;
-            mSelectedEvents.clear();
-            mComputeSelectedEvents = true;
-        } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
-            mSelectionHour = mFirstHour + mNumHours - 3;
-            mSelectedEvent = null;
-            mSelectedEvents.clear();
-            mComputeSelectedEvents = true;
-        }
-    }
-
-    private void initFirstHour() {
-        mFirstHour = mSelectionHour - mNumHours / 2;
-        if (mFirstHour < 0) {
-            mFirstHour = 0;
-        } else if (mFirstHour + mNumHours > 24) {
-            mFirstHour = 24 - mNumHours;
-        }
-    }
-
-    /**
-     * Recomputes the first full hour that is visible on screen after the
-     * screen is scrolled.
-     */
-    private void computeFirstHour() {
-        // Compute the first full hour that is visible on screen
-        mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP);
-        mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY;
-    }
-
-    private void adjustHourSelection() {
-        if (mSelectionHour < 0) {
-            mSelectionHour = 0;
-            if (mMaxAllDayEvents > 0) {
-                mPrevSelectedEvent = null;
-                mSelectionAllDay = true;
-            }
-        }
-
-        if (mSelectionHour > 23) {
-            mSelectionHour = 23;
-        }
-
-        // If the selected hour is at least 2 time slots from the top and
-        // bottom of the screen, then don't scroll the view.
-        if (mSelectionHour < mFirstHour + 1) {
-            // If there are all-days events for the selected day but there
-            // are no more normal events earlier in the day, then jump to
-            // the all-day event area.
-            // Exception 1: allow the user to scroll to 8am with the trackball
-            // before jumping to the all-day event area.
-            // Exception 2: if 12am is on screen, then allow the user to select
-            // 12am before going up to the all-day event area.
-            int daynum = mSelectionDay - mFirstJulianDay;
-            if (mMaxAllDayEvents > 0 && mEarliestStartHour[daynum] > mSelectionHour
-                    && mFirstHour > 0 && mFirstHour < 8) {
-                mPrevSelectedEvent = null;
-                mSelectionAllDay = true;
-                mSelectionHour = mFirstHour + 1;
-                return;
-            }
-
-            if (mFirstHour > 0) {
-                mFirstHour -= 1;
-                mViewStartY -= (mCellHeight + HOUR_GAP);
-                if (mViewStartY < 0) {
-                    mViewStartY = 0;
-                }
-                return;
-            }
-        }
-
-        if (mSelectionHour > mFirstHour + mNumHours - 3) {
-            if (mFirstHour < 24 - mNumHours) {
-                mFirstHour += 1;
-                mViewStartY += (mCellHeight + HOUR_GAP);
-                if (mViewStartY > mBitmapHeight - mGridAreaHeight) {
-                    mViewStartY = mBitmapHeight - mGridAreaHeight;
-                }
-                return;
-            } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
-                mViewStartY = mBitmapHeight - mGridAreaHeight;
-            }
-        }
-    }
-
-    void clearCachedEvents() {
-        mLastReloadMillis = 0;
-    }
-
-    private Runnable mCancelCallback = new Runnable() {
-        public void run() {
-            clearCachedEvents();
-        }
-    };
-
-    void reloadEvents() {
-        // Protect against this being called before this view has been
-        // initialized.
-        if (mParentActivity == null) {
-            return;
-        }
-
-        mSelectedEvent = null;
-        mPrevSelectedEvent = null;
-        mSelectedEvents.clear();
-
-        // The start date is the beginning of the week at 12am
-        Time weekStart = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
-        weekStart.set(mBaseDate);
-        weekStart.hour = 0;
-        weekStart.minute = 0;
-        weekStart.second = 0;
-        long millis = weekStart.normalize(true /* ignore isDst */);
-
-        // Avoid reloading events unnecessarily.
-        if (millis == mLastReloadMillis) {
-            return;
-        }
-        mLastReloadMillis = millis;
-
-        // load events in the background
-        mParentActivity.startProgressSpinner();
-        final ArrayList<Event> events = new ArrayList<Event>();
-        mEventLoader.loadEventsInBackground(mNumDays, events, millis, new Runnable() {
-            public void run() {
-                mEvents = events;
-                mRemeasure = true;
-                mRedrawScreen = true;
-                mComputeSelectedEvents = true;
-                recalc();
-                mParentActivity.stopProgressSpinner();
-                invalidate();
-            }
-        }, mCancelCallback);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mRemeasure) {
-            remeasure(getWidth(), getHeight());
-            mRemeasure = false;
-        }
-
-        if (mRedrawScreen && mCanvas != null) {
-            doDraw(mCanvas);
-            mRedrawScreen = false;
-        }
-
-        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            canvas.save();
-            if (mViewStartX > 0) {
-                canvas.translate(mViewWidth - mViewStartX, 0);
-            } else {
-                canvas.translate(-(mViewWidth + mViewStartX), 0);
-            }
-            CalendarView nextView = mParentActivity.getNextView();
-
-            // Prevent infinite recursive calls to onDraw().
-            nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE;
-
-            nextView.onDraw(canvas);
-            canvas.restore();
-            canvas.save();
-            canvas.translate(-mViewStartX, 0);
-        }
-
-        if (mBitmap != null) {
-            drawCalendarView(canvas);
-        }
-
-        // Draw the fixed areas (that don't scroll) directly to the canvas.
-        drawAfterScroll(canvas);
-        mComputeSelectedEvents = false;
-
-        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            canvas.restore();
-        }
-
-        sendAccessibilityEvents();
-    }
-
-    private void drawCalendarView(Canvas canvas) {
-
-        // Copy the scrollable region from the big bitmap to the canvas.
-        Rect src = mSrcRect;
-        Rect dest = mDestRect;
-
-        src.top = mViewStartY;
-        src.bottom = mViewStartY + mGridAreaHeight;
-        src.left = 0;
-        src.right = mViewWidth;
-
-        dest.top = mFirstCell;
-        dest.bottom = mViewHeight;
-        dest.left = 0;
-        dest.right = mViewWidth;
-
-        canvas.save();
-        canvas.clipRect(dest);
-        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-        canvas.drawBitmap(mBitmap, src, dest, null);
-        canvas.restore();
-    }
-
-    private void drawAfterScroll(Canvas canvas) {
-        Paint p = mPaint;
-        Rect r = mRect;
-
-        if (mMaxAllDayEvents != 0) {
-            drawAllDayEvents(mFirstJulianDay, mNumDays, r, canvas, p);
-            drawUpperLeftCorner(r, canvas, p);
-        }
-
-        if (mNumDays > 1) {
-            drawDayHeaderLoop(r, canvas, p);
-        }
-
-        // Draw the AM and PM indicators if we're in 12 hour mode
-        if (!mIs24HourFormat) {
-            drawAmPm(canvas, p);
-        }
-
-        // Update the popup window showing the event details, but only if
-        // we are not scrolling and we have focus.
-        if (!mScrolling && isFocused()) {
-            updateEventDetails();
-        }
-    }
-
-    // This isn't really the upper-left corner.  It's the square area just
-    // below the upper-left corner, above the hours and to the left of the
-    // all-day area.
-    private void drawUpperLeftCorner(Rect r, Canvas canvas, Paint p) {
-        p.setColor(mCalendarHourBackground);
-        r.top = mBannerPlusMargin;
-        r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
-        r.left = 0;
-        r.right = mHoursWidth;
-        canvas.drawRect(r, p);
-    }
-
-    private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) {
-        // Draw the horizontal day background banner
-        p.setColor(mCalendarDateBannerBackground);
-        r.top = 0;
-        r.bottom = mBannerPlusMargin;
-        r.left = 0;
-        r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
-        canvas.drawRect(r, p);
-
-        // Fill the extra space on the right side with the default background
-        r.left = r.right;
-        r.right = mViewWidth;
-        p.setColor(mCalendarGridAreaBackground);
-        canvas.drawRect(r, p);
-
-        // Draw a highlight on the selected day (if any), but only if we are
-        // displaying more than one day.
-        if (mSelectionMode != SELECTION_HIDDEN) {
-            if (mNumDays > 1) {
-                p.setColor(mCalendarDateSelected);
-                r.top = 0;
-                r.bottom = mBannerPlusMargin;
-                int daynum = mSelectionDay - mFirstJulianDay;
-                r.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
-                r.right = r.left + mCellWidth;
-                canvas.drawRect(r, p);
-            }
-        }
-
-        p.setTextSize(NORMAL_FONT_SIZE);
-        p.setTextAlign(Paint.Align.CENTER);
-        int x = mHoursWidth;
-        int deltaX = mCellWidth + DAY_GAP;
-        int cell = mFirstJulianDay;
-
-        String[] dayNames;
-        if (mDateStrWidth < mCellWidth) {
-            dayNames = mDayStrs;
-        } else {
-            dayNames = mDayStrs2Letter;
-        }
-
-        p.setTypeface(mBold);
-        p.setAntiAlias(true);
-        for (int day = 0; day < mNumDays; day++, cell++) {
-            drawDayHeader(dayNames[day + mStartDay], day, cell, x, canvas, p);
-            x += deltaX;
-        }
-    }
-
-    private void drawAmPm(Canvas canvas, Paint p) {
-        p.setColor(mCalendarAmPmLabel);
-        p.setTextSize(AMPM_FONT_SIZE);
-        p.setTypeface(mBold);
-        p.setAntiAlias(true);
-        mPaint.setTextAlign(Paint.Align.RIGHT);
-        String text = mAmString;
-        if (mFirstHour >= 12) {
-            text = mPmString;
-        }
-        int y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP;
-        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
-        canvas.drawText(text, right, y, p);
-
-        if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
-            // Also draw the "PM"
-            text = mPmString;
-            y = mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP)
-                    + 2 * mHoursTextHeight + HOUR_GAP;
-            canvas.drawText(text, right, y, p);
-        }
-    }
-
-    private void drawCurrentTimeMarker(int top, Canvas canvas, Paint p) {
-        Rect r = new Rect();
-        r.top = top - CURRENT_TIME_LINE_HEIGHT / 2;
-        r.bottom = top + CURRENT_TIME_LINE_HEIGHT / 2;
-        r.left = 0;
-        r.right = mHoursWidth;
-
-        p.setColor(mCurrentTimeMarkerColor);
-        canvas.drawRect(r, p);
-    }
-
-    private void drawCurrentTimeLine(Rect r, int left, int top, Canvas canvas, Paint p) {
-        //Do a white outline so it'll show up on a red event
-        p.setColor(mCurrentTimeMarkerBorderColor);
-        r.top = top - CURRENT_TIME_LINE_HEIGHT / 2 - CURRENT_TIME_LINE_BORDER_WIDTH;
-        r.bottom = top + CURRENT_TIME_LINE_HEIGHT / 2 + CURRENT_TIME_LINE_BORDER_WIDTH;
-        r.left = left + CURRENT_TIME_LINE_SIDE_BUFFER;
-        r.right = r.left + mCellWidth - 2 * CURRENT_TIME_LINE_SIDE_BUFFER;
-        canvas.drawRect(r, p);
-        //Then draw the red line
-        p.setColor(mCurrentTimeLineColor);
-        r.top = top - CURRENT_TIME_LINE_HEIGHT / 2;
-        r.bottom = top + CURRENT_TIME_LINE_HEIGHT / 2;
-        canvas.drawRect(r, p);
-    }
-
-    private void doDraw(Canvas canvas) {
-        Paint p = mPaint;
-        Rect r = mRect;
-        int lineY = mCurrentTime.hour*(mCellHeight + HOUR_GAP)
-            + ((mCurrentTime.minute * mCellHeight) / 60)
-            + 1;
-
-        drawGridBackground(r, canvas, p);
-        drawHours(r, canvas, p);
-
-        // Draw each day
-        int x = mHoursWidth;
-        int deltaX = mCellWidth + DAY_GAP;
-        int cell = mFirstJulianDay;
-        for (int day = 0; day < mNumDays; day++, cell++) {
-            drawEvents(cell, x, HOUR_GAP, canvas, p);
-            //If this is today
-            if(cell == mTodayJulianDay) {
-                //And the current time shows up somewhere on the screen
-                if(lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
-                    //draw both the marker and the line
-                    drawCurrentTimeMarker(lineY, canvas, p);
-                    drawCurrentTimeLine(r, x, lineY, canvas, p);
-                }
-            }
-            x += deltaX;
-        }
-    }
-
-    private void drawHours(Rect r, Canvas canvas, Paint p) {
-        // Draw the background for the hour labels
-        p.setColor(mCalendarHourBackground);
-        r.top = 0;
-        r.bottom = 24 * (mCellHeight + HOUR_GAP) + HOUR_GAP;
-        r.left = 0;
-        r.right = mHoursWidth;
-        canvas.drawRect(r, p);
-
-        // Fill the bottom left corner with the default grid background
-        r.top = r.bottom;
-        r.bottom = mBitmapHeight;
-        p.setColor(mCalendarGridAreaBackground);
-        canvas.drawRect(r, p);
-
-        // Draw a highlight on the selected hour (if needed)
-        if (mSelectionMode != SELECTION_HIDDEN && !mSelectionAllDay) {
-            p.setColor(mCalendarHourSelected);
-            r.top = mSelectionHour * (mCellHeight + HOUR_GAP);
-            r.bottom = r.top + mCellHeight + 2 * HOUR_GAP;
-            r.left = 0;
-            r.right = mHoursWidth;
-            canvas.drawRect(r, p);
-
-            boolean drawBorder = false;
-            if (!drawBorder) {
-                r.top += HOUR_GAP;
-                r.bottom -= HOUR_GAP;
-            }
-
-            // Also draw the highlight on the grid
-            p.setColor(mCalendarGridAreaSelected);
-            int daynum = mSelectionDay - mFirstJulianDay;
-            r.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
-            r.right = r.left + mCellWidth;
-            canvas.drawRect(r, p);
-
-            // Draw a border around the highlighted grid hour.
-            if (drawBorder) {
-                Path path = mPath;
-                r.top += HOUR_GAP;
-                r.bottom -= HOUR_GAP;
-                path.reset();
-                path.addRect(r.left, r.top, r.right, r.bottom, Direction.CW);
-                canvas.drawPath(path, mSelectionPaint);
-            }
-
-            saveSelectionPosition(r.left, r.top, r.right, r.bottom);
-        }
-
-        p.setColor(mCalendarHourLabel);
-        p.setTextSize(HOURS_FONT_SIZE);
-        p.setTypeface(mBold);
-        p.setTextAlign(Paint.Align.RIGHT);
-        p.setAntiAlias(true);
-
-        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
-        int y = HOUR_GAP + mHoursTextHeight;
-
-        for (int i = 0; i < 24; i++) {
-            String time = mHourStrs[i];
-            canvas.drawText(time, right, y, p);
-            y += mCellHeight + HOUR_GAP;
-        }
-    }
-
-    private void sendAccessibilityEvents() {
-        if (!isShown() || !AccessibilityManager.getInstance(mContext).isEnabled()) {
-            return;
-        }
-        // if the title text has changed => announce period
-        CharSequence titleTextViewText = mTitleTextView.getText();
-        // intended use of identity comparison
-        boolean titleChanged = titleTextViewText != mPrevTitleTextViewText;
-        if (titleChanged) {
-            mPrevTitleTextViewText = titleTextViewText;
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-        // if title or selection has changed => announce selection
-        // Note: if the title has changed we want to send both events
-        if (titleChanged || mPrevSelectionDay != mSelectionDay
-                || mPrevSelectionHour != mSelectionHour) {
-            mPrevSelectionDay = mSelectionDay;
-            mPrevSelectionHour = mSelectionHour;
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-        }
-    }
-
-    @Override
-    public void sendAccessibilityEvent(int eventType) {
-        // we send only selection events since semantically we select
-        // certain element and not always this view gets focus which
-        // triggers firing of a focus accessibility event
-        if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
-            return;
-        }
-        super.sendAccessibilityEvent(eventType);
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-            // add the currently shown period (day/week)
-            if (mNumDays == 1) {
-                // for daily view the title has enough context information
-                event.getText().add(mTitleTextView.getText());
-            } else {
-                // since the title view does not contain enough context we
-                // compute a more descriptive title for the shown time frame
-                int flags = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH
-                        | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
-                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-                if (DateFormat.is24HourFormat(mParentActivity)) {
-                    flags |= DateUtils.FORMAT_24HOUR;
-                }
-
-                long start = mBaseDate.toMillis(false);
-                long gmtOff = mBaseDate.gmtoff;
-                int firstJulianDay = Time.getJulianDay(start, gmtOff);
-
-                Time time = new Time(mBaseDate);
-                time.setJulianDay(firstJulianDay);
-                long startTime = time.normalize(true);
-                time.setJulianDay(firstJulianDay + mNumDays);
-                long endTime = time.normalize(true);
-
-                String timeRange = Utils.formatDateRange(mParentActivity, startTime, endTime,
-                        flags);
-                event.getText().add(timeRange);
-            }
-        } else if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SELECTED) {
-            int flags = 0;
-            // add the selection
-            if (mNumDays == 1) {
-                // if day view we need only hour information
-                flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-            } else {
-                flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE
-                        | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-            }
-            long startTime = getSelectedTimeInMillis();
-            long endTime = startTime + MILLIS_PER_HOUR;
-            if (DateFormat.is24HourFormat(mParentActivity)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            String timeRange = Utils.formatDateRange(mParentActivity, startTime, endTime,
-                    flags);
-            event.getText().add(timeRange);
-
-            // add the selected event data if such
-            if (mSelectedEvent != null) {
-                Event selectedEvent = mSelectedEvent;
-                if (mTempEventBundle == null) {
-                    mTempEventBundle = new Bundle();
-                }
-                Bundle bundle = mTempEventBundle;
-                bundle.clear();
-                bundle.putLong("id", selectedEvent.id);
-                bundle.putInt("color", selectedEvent.color);
-                bundle.putCharSequence("title", selectedEvent.title);
-                bundle.putCharSequence("location", selectedEvent.location);
-                bundle.putBoolean("allDay", selectedEvent.allDay);
-                bundle.putInt("startDay", selectedEvent.startDay);
-                bundle.putInt("endDay", selectedEvent.endDay);
-                bundle.putInt("startTime", selectedEvent.startTime);
-                bundle.putInt("endTime", selectedEvent.endTime);
-                bundle.putLong("startMillis", selectedEvent.startMillis);
-                bundle.putLong("endMillis", selectedEvent.endMillis);
-                bundle.putString("organizer", selectedEvent.organizer);
-                bundle.putBoolean("guestsCanModify", selectedEvent.guestsCanModify);
-                event.setParcelableData(bundle);
-            }
-        }
-
-        // add day event count, events for same hour count and
-        // the index of the selected event for the same hour
-        int todayEventCount = 0;
-        int sameHourEventCount = 0;
-        int currentSameHourEventIndex = 0;
-        int selectionHourStart = mSelectionHour * MINUTES_PER_HOUR;
-        int selectionHourEnd = selectionHourStart + MINUTES_PER_HOUR;
-        for (int i = 0, count = mEvents.size(); i < count; i++) {
-            Event calendarEvent = mEvents.get(i);
-            if (calendarEvent.endDay == mSelectionDay) {
-                todayEventCount++;
-                if (selectionHourStart >= calendarEvent.endTime
-                        || selectionHourEnd <= calendarEvent.startTime) {
-                    continue;
-                }
-                if (calendarEvent == mSelectedEvent) {
-                    currentSameHourEventIndex = sameHourEventCount;
-                }
-                sameHourEventCount++;
-            }
-        }
-        event.setAddedCount(todayEventCount);
-        event.setItemCount(sameHourEventCount);
-        event.setCurrentItemIndex(currentSameHourEventIndex);
-
-        return true;
-    }
-
-    private void drawDayHeader(String dateStr, int day, int cell, int x, Canvas canvas, Paint p) {
-        float xCenter = x + mCellWidth / 2.0f;
-
-        if (Utils.isSaturday(day, mStartDay)) {
-            p.setColor(mWeek_saturdayColor);
-        } else if (Utils.isSunday(day, mStartDay)) {
-            p.setColor(mWeek_sundayColor);
-        } else {
-            p.setColor(mCalendarDateBannerTextColor);
-        }
-
-        int dateNum = mFirstDate + day;
-        if (dateNum > mMonthLength) {
-            dateNum -= mMonthLength;
-        }
-
-        String dateNumStr;
-        // Add a leading zero if the date is a single digit
-        if (dateNum < 10) {
-            dateNumStr = "0" + dateNum;
-        } else {
-            dateNumStr = String.valueOf(dateNum);
-        }
-
-        DayHeader header = dayHeaders[day];
-        if (header == null || header.cell != cell) {
-            // The day header string is regenerated on every draw during drag and fling animation.
-            // Caching day header since formatting the string takes surprising long time.
-
-            dayHeaders[day] = new DayHeader();
-            dayHeaders[day].cell = cell;
-            dayHeaders[day].dateString = getResources().getString(
-                    R.string.weekday_day, dateStr, dateNumStr);
-        }
-        dateStr = dayHeaders[day].dateString;
-
-        float y = mBannerPlusMargin - 7;
-        canvas.drawText(dateStr, xCenter, y, p);
-    }
-
-    private void drawGridBackground(Rect r, Canvas canvas, Paint p) {
-        Paint.Style savedStyle = p.getStyle();
-
-        // Clear the background
-        p.setColor(mCalendarGridAreaBackground);
-        r.top = 0;
-        r.bottom = mBitmapHeight;
-        r.left = 0;
-        r.right = mViewWidth;
-        canvas.drawRect(r, p);
-
-        // Draw the horizontal grid lines
-        p.setColor(mCalendarGridLineHorizontalColor);
-        p.setStyle(Style.STROKE);
-        p.setStrokeWidth(0);
-        p.setAntiAlias(false);
-        float startX = mHoursWidth;
-        float stopX = mHoursWidth + (mCellWidth + DAY_GAP) * mNumDays;
-        float y = 0;
-        float deltaY = mCellHeight + HOUR_GAP;
-        for (int hour = 0; hour <= 24; hour++) {
-            canvas.drawLine(startX, y, stopX, y, p);
-            y += deltaY;
-        }
-
-        // Draw the vertical grid lines
-        p.setColor(mCalendarGridLineVerticalColor);
-        float startY = 0;
-        float stopY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
-        float deltaX = mCellWidth + DAY_GAP;
-        float x = mHoursWidth + mCellWidth;
-        for (int day = 0; day < mNumDays; day++) {
-            canvas.drawLine(x, startY, x, stopY, p);
-            x += deltaX;
-        }
-
-        // Restore the saved style.
-        p.setStyle(savedStyle);
-        p.setAntiAlias(true);
-    }
-
-    Event getSelectedEvent() {
-        if (mSelectedEvent == null) {
-            // There is no event at the selected hour, so create a new event.
-            return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
-                    getSelectedMinutesSinceMidnight());
-        }
-        return mSelectedEvent;
-    }
-
-    boolean isEventSelected() {
-        return (mSelectedEvent != null);
-    }
-
-    Event getNewEvent() {
-        return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
-                getSelectedMinutesSinceMidnight());
-    }
-
-    static Event getNewEvent(int julianDay, long utcMillis,
-            int minutesSinceMidnight) {
-        Event event = Event.newInstance();
-        event.startDay = julianDay;
-        event.endDay = julianDay;
-        event.startMillis = utcMillis;
-        event.endMillis = event.startMillis + MILLIS_PER_HOUR;
-        event.startTime = minutesSinceMidnight;
-        event.endTime = event.startTime + MINUTES_PER_HOUR;
-        return event;
-    }
-
-    private int computeMaxStringWidth(int currentMax, String[] strings, Paint p) {
-        float maxWidthF = 0.0f;
-
-        int len = strings.length;
-        for (int i = 0; i < len; i++) {
-            float width = p.measureText(strings[i]);
-            maxWidthF = Math.max(width, maxWidthF);
-        }
-        int maxWidth = (int) (maxWidthF + 0.5);
-        if (maxWidth < currentMax) {
-            maxWidth = currentMax;
-        }
-        return maxWidth;
-    }
-
-    private void saveSelectionPosition(float left, float top, float right, float bottom) {
-        mPrevBox.left = (int) left;
-        mPrevBox.right = (int) right;
-        mPrevBox.top = (int) top;
-        mPrevBox.bottom = (int) bottom;
-    }
-
-    private Rect getCurrentSelectionPosition() {
-        Rect box = new Rect();
-        box.top = mSelectionHour * (mCellHeight + HOUR_GAP);
-        box.bottom = box.top + mCellHeight + HOUR_GAP;
-        int daynum = mSelectionDay - mFirstJulianDay;
-        box.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
-        box.right = box.left + mCellWidth + DAY_GAP;
-        return box;
-    }
-
-    private void drawAllDayEvents(int firstDay, int numDays,
-            Rect r, Canvas canvas, Paint p) {
-        p.setTextSize(NORMAL_FONT_SIZE);
-        p.setTextAlign(Paint.Align.LEFT);
-        Paint eventTextPaint = mEventTextPaint;
-
-        // Draw the background for the all-day events area
-        r.top = mBannerPlusMargin;
-        r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
-        r.left = mHoursWidth;
-        r.right = r.left + mNumDays * (mCellWidth + DAY_GAP);
-        p.setColor(mCalendarAllDayBackground);
-        canvas.drawRect(r, p);
-
-        // Fill the extra space on the right side with the default background
-        r.left = r.right;
-        r.right = mViewWidth;
-        p.setColor(mCalendarGridAreaBackground);
-        canvas.drawRect(r, p);
-
-        // Draw the vertical grid lines
-        p.setColor(mCalendarGridLineVerticalColor);
-        p.setStyle(Style.STROKE);
-        p.setStrokeWidth(0);
-        p.setAntiAlias(false);
-        float startY = r.top;
-        float stopY = r.bottom;
-        float deltaX = mCellWidth + DAY_GAP;
-        float x = mHoursWidth + mCellWidth;
-        for (int day = 0; day <= mNumDays; day++) {
-            canvas.drawLine(x, startY, x, stopY, p);
-            x += deltaX;
-        }
-        p.setAntiAlias(true);
-        p.setStyle(Style.FILL);
-
-        int y = mBannerPlusMargin + ALLDAY_TOP_MARGIN;
-        float left = mHoursWidth;
-        int lastDay = firstDay + numDays - 1;
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        float drawHeight = mAllDayHeight;
-        float numRectangles = mMaxAllDayEvents;
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            if (!event.allDay)
-                continue;
-            int startDay = event.startDay;
-            int endDay = event.endDay;
-            if (startDay > lastDay || endDay < firstDay)
-                continue;
-            if (startDay < firstDay)
-                startDay = firstDay;
-            if (endDay > lastDay)
-                endDay = lastDay;
-            int startIndex = startDay - firstDay;
-            int endIndex = endDay - firstDay;
-            float height = drawHeight / numRectangles;
-
-            // Prevent a single event from getting too big
-            if (height > MAX_ALLDAY_EVENT_HEIGHT) {
-                height = MAX_ALLDAY_EVENT_HEIGHT;
-            }
-
-            // Leave a one-pixel space between the vertical day lines and the
-            // event rectangle.
-            event.left = left + startIndex * (mCellWidth + DAY_GAP) + 2;
-            event.right = left + endIndex * (mCellWidth + DAY_GAP) + mCellWidth - 1;
-            event.top = y + height * event.getColumn();
-
-            // Multiply the height by 0.9 to leave a little gap between events
-            event.bottom = event.top + height * 0.9f;
-
-            RectF rf = drawAllDayEventRect(event, canvas, p, eventTextPaint);
-            drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
-
-            // Check if this all-day event intersects the selected day
-            if (mSelectionAllDay && mComputeSelectedEvents) {
-                if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
-                    mSelectedEvents.add(event);
-                }
-            }
-        }
-
-        if (mSelectionAllDay) {
-            // Compute the neighbors for the list of all-day events that
-            // intersect the selected day.
-            computeAllDayNeighbors();
-            if (mSelectedEvent != null) {
-                Event event = mSelectedEvent;
-                RectF rf = drawAllDayEventRect(event, canvas, p, eventTextPaint);
-                drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
-            }
-
-            // Draw the highlight on the selected all-day area
-            float top = mBannerPlusMargin + 1;
-            float bottom = top + mAllDayHeight + ALLDAY_TOP_MARGIN - 1;
-            int daynum = mSelectionDay - mFirstJulianDay;
-            left = mHoursWidth + daynum * (mCellWidth + DAY_GAP) + 1;
-            float right = left + mCellWidth + DAY_GAP - 1;
-            if (mNumDays == 1) {
-                // The Day view doesn't have a vertical line on the right.
-                right -= 1;
-            }
-            Path path = mPath;
-            path.reset();
-            path.addRect(left, top, right, bottom, Direction.CW);
-            canvas.drawPath(path, mSelectionPaint);
-
-            // Set the selection position to zero so that when we move down
-            // to the normal event area, we will highlight the topmost event.
-            saveSelectionPosition(0f, 0f, 0f, 0f);
-        }
-    }
-
-    private void computeAllDayNeighbors() {
-        int len = mSelectedEvents.size();
-        if (len == 0 || mSelectedEvent != null) {
-            return;
-        }
-
-        // First, clear all the links
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-            ev.nextUp = null;
-            ev.nextDown = null;
-            ev.nextLeft = null;
-            ev.nextRight = null;
-        }
-
-        // For each event in the selected event list "mSelectedEvents", find
-        // its neighbors in the up and down directions.  This could be done
-        // more efficiently by sorting on the Event.getColumn() field, but
-        // the list is expected to be very small.
-
-        // Find the event in the same row as the previously selected all-day
-        // event, if any.
-        int startPosition = -1;
-        if (mPrevSelectedEvent != null && mPrevSelectedEvent.allDay) {
-            startPosition = mPrevSelectedEvent.getColumn();
-        }
-        int maxPosition = -1;
-        Event startEvent = null;
-        Event maxPositionEvent = null;
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-            int position = ev.getColumn();
-            if (position == startPosition) {
-                startEvent = ev;
-            } else if (position > maxPosition) {
-                maxPositionEvent = ev;
-                maxPosition = position;
-            }
-            for (int jj = 0; jj < len; jj++) {
-                if (jj == ii) {
-                    continue;
-                }
-                Event neighbor = mSelectedEvents.get(jj);
-                int neighborPosition = neighbor.getColumn();
-                if (neighborPosition == position - 1) {
-                    ev.nextUp = neighbor;
-                } else if (neighborPosition == position + 1) {
-                    ev.nextDown = neighbor;
-                }
-            }
-        }
-        if (startEvent != null) {
-            mSelectedEvent = startEvent;
-        } else {
-            mSelectedEvent = maxPositionEvent;
-        }
-    }
-
-    RectF drawAllDayEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
-        // If this event is selected, then use the selection color
-        if (mSelectedEvent == event) {
-            // Also, remember the last selected event that we drew
-            mPrevSelectedEvent = event;
-            p.setColor(mSelectionColor);
-            eventTextPaint.setColor(mSelectedEventTextColor);
-        } else {
-            // Use the normal color for all-day events
-            p.setColor(event.color);
-            eventTextPaint.setColor(mEventTextColor);
-        }
-
-        RectF rf = mRectF;
-        rf.top = event.top;
-        rf.bottom = event.bottom;
-        rf.left = event.left;
-        rf.right = event.right;
-        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
-
-        rf.left += 2;
-        rf.right -= 2;
-        return rf;
-    }
-
-    private void drawEvents(int date, int left, int top, Canvas canvas, Paint p) {
-        Paint eventTextPaint = mEventTextPaint;
-        int cellWidth = mCellWidth;
-        int cellHeight = mCellHeight;
-
-        // Use the selected hour as the selection region
-        Rect selectionArea = mRect;
-        selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
-        selectionArea.bottom = selectionArea.top + cellHeight;
-        selectionArea.left = left;
-        selectionArea.right = selectionArea.left + cellWidth;
-
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        EventGeometry geometry = mEventGeometry;
-
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
-                continue;
-            }
-
-            if (date == mSelectionDay && !mSelectionAllDay && mComputeSelectedEvents
-                    && geometry.eventIntersectsSelection(event, selectionArea)) {
-                mSelectedEvents.add(event);
-            }
-
-            RectF rf = drawEventRect(event, canvas, p, eventTextPaint);
-            drawEventText(event, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
-        }
-
-        if (date == mSelectionDay && !mSelectionAllDay && isFocused()
-                && mSelectionMode != SELECTION_HIDDEN) {
-            computeNeighbors();
-            if (mSelectedEvent != null) {
-                RectF rf = drawEventRect(mSelectedEvent, canvas, p, eventTextPaint);
-                drawEventText(mSelectedEvent, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
-            }
-        }
-    }
-
-    // Computes the "nearest" neighbor event in four directions (left, right,
-    // up, down) for each of the events in the mSelectedEvents array.
-    private void computeNeighbors() {
-        int len = mSelectedEvents.size();
-        if (len == 0 || mSelectedEvent != null) {
-            return;
-        }
-
-        // First, clear all the links
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-            ev.nextUp = null;
-            ev.nextDown = null;
-            ev.nextLeft = null;
-            ev.nextRight = null;
-        }
-
-        Event startEvent = mSelectedEvents.get(0);
-        int startEventDistance1 = 100000;  // any large number
-        int startEventDistance2 = 100000;  // any large number
-        int prevLocation = FROM_NONE;
-        int prevTop;
-        int prevBottom;
-        int prevLeft;
-        int prevRight;
-        int prevCenter = 0;
-        Rect box = getCurrentSelectionPosition();
-        if (mPrevSelectedEvent != null) {
-            prevTop = (int) mPrevSelectedEvent.top;
-            prevBottom = (int) mPrevSelectedEvent.bottom;
-            prevLeft = (int) mPrevSelectedEvent.left;
-            prevRight = (int) mPrevSelectedEvent.right;
-            // Check if the previously selected event intersects the previous
-            // selection box.  (The previously selected event may be from a
-            // much older selection box.)
-            if (prevTop >= mPrevBox.bottom || prevBottom <= mPrevBox.top
-                    || prevRight <= mPrevBox.left || prevLeft >= mPrevBox.right) {
-                mPrevSelectedEvent = null;
-                prevTop = mPrevBox.top;
-                prevBottom = mPrevBox.bottom;
-                prevLeft = mPrevBox.left;
-                prevRight = mPrevBox.right;
-            } else {
-                // Clip the top and bottom to the previous selection box.
-                if (prevTop < mPrevBox.top) {
-                    prevTop = mPrevBox.top;
-                }
-                if (prevBottom > mPrevBox.bottom) {
-                    prevBottom = mPrevBox.bottom;
-                }
-            }
-        } else {
-            // Just use the previously drawn selection box
-            prevTop = mPrevBox.top;
-            prevBottom = mPrevBox.bottom;
-            prevLeft = mPrevBox.left;
-            prevRight = mPrevBox.right;
-        }
-
-        // Figure out where we came from and compute the center of that area.
-        if (prevLeft >= box.right) {
-            // The previously selected event was to the right of us.
-            prevLocation = FROM_RIGHT;
-            prevCenter = (prevTop + prevBottom) / 2;
-        } else if (prevRight <= box.left) {
-            // The previously selected event was to the left of us.
-            prevLocation = FROM_LEFT;
-            prevCenter = (prevTop + prevBottom) / 2;
-        } else if (prevBottom <= box.top) {
-            // The previously selected event was above us.
-            prevLocation = FROM_ABOVE;
-            prevCenter = (prevLeft + prevRight) / 2;
-        } else if (prevTop >= box.bottom) {
-            // The previously selected event was below us.
-            prevLocation = FROM_BELOW;
-            prevCenter = (prevLeft + prevRight) / 2;
-        }
-
-        // For each event in the selected event list "mSelectedEvents", search
-        // all the other events in that list for the nearest neighbor in 4
-        // directions.
-        for (int ii = 0; ii < len; ii++) {
-            Event ev = mSelectedEvents.get(ii);
-
-            int startTime = ev.startTime;
-            int endTime = ev.endTime;
-            int left = (int) ev.left;
-            int right = (int) ev.right;
-            int top = (int) ev.top;
-            if (top < box.top) {
-                top = box.top;
-            }
-            int bottom = (int) ev.bottom;
-            if (bottom > box.bottom) {
-                bottom = box.bottom;
-            }
-            if (false) {
-                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
-                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-                if (DateFormat.is24HourFormat(mParentActivity)) {
-                    flags |= DateUtils.FORMAT_24HOUR;
-                }
-                String timeRange = Utils.formatDateRange(mParentActivity,
-                        ev.startMillis, ev.endMillis, flags);
-                Log.i("Cal", "left: " + left + " right: " + right + " top: " + top
-                        + " bottom: " + bottom + " ev: " + timeRange + " " + ev.title);
-            }
-            int upDistanceMin = 10000;     // any large number
-            int downDistanceMin = 10000;   // any large number
-            int leftDistanceMin = 10000;   // any large number
-            int rightDistanceMin = 10000;  // any large number
-            Event upEvent = null;
-            Event downEvent = null;
-            Event leftEvent = null;
-            Event rightEvent = null;
-
-            // Pick the starting event closest to the previously selected event,
-            // if any.  distance1 takes precedence over distance2.
-            int distance1 = 0;
-            int distance2 = 0;
-            if (prevLocation == FROM_ABOVE) {
-                if (left >= prevCenter) {
-                    distance1 = left - prevCenter;
-                } else if (right <= prevCenter) {
-                    distance1 = prevCenter - right;
-                }
-                distance2 = top - prevBottom;
-            } else if (prevLocation == FROM_BELOW) {
-                if (left >= prevCenter) {
-                    distance1 = left - prevCenter;
-                } else if (right <= prevCenter) {
-                    distance1 = prevCenter - right;
-                }
-                distance2 = prevTop - bottom;
-            } else if (prevLocation == FROM_LEFT) {
-                if (bottom <= prevCenter) {
-                    distance1 = prevCenter - bottom;
-                } else if (top >= prevCenter) {
-                    distance1 = top - prevCenter;
-                }
-                distance2 = left - prevRight;
-            } else if (prevLocation == FROM_RIGHT) {
-                if (bottom <= prevCenter) {
-                    distance1 = prevCenter - bottom;
-                } else if (top >= prevCenter) {
-                    distance1 = top - prevCenter;
-                }
-                distance2 = prevLeft - right;
-            }
-            if (distance1 < startEventDistance1
-                    || (distance1 == startEventDistance1 && distance2 < startEventDistance2)) {
-                startEvent = ev;
-                startEventDistance1 = distance1;
-                startEventDistance2 = distance2;
-            }
-
-            // For each neighbor, figure out if it is above or below or left
-            // or right of me and compute the distance.
-            for (int jj = 0; jj < len; jj++) {
-                if (jj == ii) {
-                    continue;
-                }
-                Event neighbor = mSelectedEvents.get(jj);
-                int neighborLeft = (int) neighbor.left;
-                int neighborRight = (int) neighbor.right;
-                if (neighbor.endTime <= startTime) {
-                    // This neighbor is entirely above me.
-                    // If we overlap the same column, then compute the distance.
-                    if (neighborLeft < right && neighborRight > left) {
-                        int distance = startTime - neighbor.endTime;
-                        if (distance < upDistanceMin) {
-                            upDistanceMin = distance;
-                            upEvent = neighbor;
-                        } else if (distance == upDistanceMin) {
-                            int center = (left + right) / 2;
-                            int currentDistance = 0;
-                            int currentLeft = (int) upEvent.left;
-                            int currentRight = (int) upEvent.right;
-                            if (currentRight <= center) {
-                                currentDistance = center - currentRight;
-                            } else if (currentLeft >= center) {
-                                currentDistance = currentLeft - center;
-                            }
-
-                            int neighborDistance = 0;
-                            if (neighborRight <= center) {
-                                neighborDistance = center - neighborRight;
-                            } else if (neighborLeft >= center) {
-                                neighborDistance = neighborLeft - center;
-                            }
-                            if (neighborDistance < currentDistance) {
-                                upDistanceMin = distance;
-                                upEvent = neighbor;
-                            }
-                        }
-                    }
-                } else if (neighbor.startTime >= endTime) {
-                    // This neighbor is entirely below me.
-                    // If we overlap the same column, then compute the distance.
-                    if (neighborLeft < right && neighborRight > left) {
-                        int distance = neighbor.startTime - endTime;
-                        if (distance < downDistanceMin) {
-                            downDistanceMin = distance;
-                            downEvent = neighbor;
-                        } else if (distance == downDistanceMin) {
-                            int center = (left + right) / 2;
-                            int currentDistance = 0;
-                            int currentLeft = (int) downEvent.left;
-                            int currentRight = (int) downEvent.right;
-                            if (currentRight <= center) {
-                                currentDistance = center - currentRight;
-                            } else if (currentLeft >= center) {
-                                currentDistance = currentLeft - center;
-                            }
-
-                            int neighborDistance = 0;
-                            if (neighborRight <= center) {
-                                neighborDistance = center - neighborRight;
-                            } else if (neighborLeft >= center) {
-                                neighborDistance = neighborLeft - center;
-                            }
-                            if (neighborDistance < currentDistance) {
-                                downDistanceMin = distance;
-                                downEvent = neighbor;
-                            }
-                        }
-                    }
-                }
-
-                if (neighborLeft >= right) {
-                    // This neighbor is entirely to the right of me.
-                    // Take the closest neighbor in the y direction.
-                    int center = (top + bottom) / 2;
-                    int distance = 0;
-                    int neighborBottom = (int) neighbor.bottom;
-                    int neighborTop = (int) neighbor.top;
-                    if (neighborBottom <= center) {
-                        distance = center - neighborBottom;
-                    } else if (neighborTop >= center) {
-                        distance = neighborTop - center;
-                    }
-                    if (distance < rightDistanceMin) {
-                        rightDistanceMin = distance;
-                        rightEvent = neighbor;
-                    } else if (distance == rightDistanceMin) {
-                        // Pick the closest in the x direction
-                        int neighborDistance = neighborLeft - right;
-                        int currentDistance = (int) rightEvent.left - right;
-                        if (neighborDistance < currentDistance) {
-                            rightDistanceMin = distance;
-                            rightEvent = neighbor;
-                        }
-                    }
-                } else if (neighborRight <= left) {
-                    // This neighbor is entirely to the left of me.
-                    // Take the closest neighbor in the y direction.
-                    int center = (top + bottom) / 2;
-                    int distance = 0;
-                    int neighborBottom = (int) neighbor.bottom;
-                    int neighborTop = (int) neighbor.top;
-                    if (neighborBottom <= center) {
-                        distance = center - neighborBottom;
-                    } else if (neighborTop >= center) {
-                        distance = neighborTop - center;
-                    }
-                    if (distance < leftDistanceMin) {
-                        leftDistanceMin = distance;
-                        leftEvent = neighbor;
-                    } else if (distance == leftDistanceMin) {
-                        // Pick the closest in the x direction
-                        int neighborDistance = left - neighborRight;
-                        int currentDistance = left - (int) leftEvent.right;
-                        if (neighborDistance < currentDistance) {
-                            leftDistanceMin = distance;
-                            leftEvent = neighbor;
-                        }
-                    }
-                }
-            }
-            ev.nextUp = upEvent;
-            ev.nextDown = downEvent;
-            ev.nextLeft = leftEvent;
-            ev.nextRight = rightEvent;
-        }
-        mSelectedEvent = startEvent;
-    }
-
-
-    private RectF drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
-
-        int color = event.color;
-
-        // Fade visible boxes if event was declined.
-        boolean declined = (event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED);
-        if (declined) {
-            int alpha = color & 0xff000000;
-            color &= 0x00ffffff;
-            int red = (color & 0x00ff0000) >> 16;
-            int green = (color & 0x0000ff00) >> 8;
-            int blue = (color & 0x0000ff);
-            color = ((red >> 1) << 16) | ((green >> 1) << 8) | (blue >> 1);
-            color += 0x7F7F7F + alpha;
-        }
-
-        // If this event is selected, then use the selection color
-        if (mSelectedEvent == event) {
-            if (mSelectionMode == SELECTION_PRESSED || mSelectionMode == SELECTION_SELECTED) {
-                // Also, remember the last selected event that we drew
-                mPrevSelectedEvent = event;
-                p.setColor(mSelectionColor);
-                eventTextPaint.setColor(mSelectedEventTextColor);
-            } else if (mSelectionMode == SELECTION_LONGPRESS) {
-                p.setColor(mSelectionColor);
-                eventTextPaint.setColor(mSelectedEventTextColor);
-            } else {
-                p.setColor(color);
-                eventTextPaint.setColor(mEventTextColor);
-            }
-        } else {
-            p.setColor(color);
-            eventTextPaint.setColor(mEventTextColor);
-        }
-
-
-        RectF rf = mRectF;
-        rf.top = event.top;
-        rf.bottom = event.bottom;
-        rf.left = event.left;
-        rf.right = event.right - 1;
-
-        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
-
-        // Draw a darker border
-        float[] hsv = new float[3];
-        Color.colorToHSV(p.getColor(), hsv);
-        hsv[1] = 1.0f;
-        hsv[2] *= 0.75f;
-        mPaintBorder.setColor(Color.HSVToColor(hsv));
-        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, mPaintBorder);
-
-        rf.left += 2;
-        rf.right -= 2;
-
-        return rf;
-    }
-
-    private Pattern drawTextSanitizerFilter = Pattern.compile("[\t\n],");
-
-    // Sanitize a string before passing it to drawText or else we get little
-    // squares. For newlines and tabs before a comma, delete the character.
-    // Otherwise, just replace them with a space.
-    private String drawTextSanitizer(String string) {
-        Matcher m = drawTextSanitizerFilter.matcher(string);
-        string = m.replaceAll(",").replace('\n', ' ').replace('\n', ' ');
-        return string;
-    }
-
-    private void drawEventText(Event event, RectF rf, Canvas canvas, Paint p, int topMargin) {
-        if (!mDrawTextInEventRect) {
-            return;
-        }
-
-        float width = rf.right - rf.left;
-        float height = rf.bottom - rf.top;
-
-        // Leave one pixel extra space between lines
-        int lineHeight = mEventTextHeight + 1;
-
-        // If the rectangle is too small for text, then return
-        if (width < MIN_CELL_WIDTH_FOR_TEXT || height <= lineHeight) {
-            return;
-        }
-
-        // Truncate the event title to a known (large enough) limit
-        String text = event.getTitleAndLocation();
-
-        text = drawTextSanitizer(text);
-
-        int len = text.length();
-        if (len > MAX_EVENT_TEXT_LEN) {
-            text = text.substring(0, MAX_EVENT_TEXT_LEN);
-            len = MAX_EVENT_TEXT_LEN;
-        }
-
-        // Figure out how much space the event title will take, and create a
-        // String fragment that will fit in the rectangle.  Use multiple lines,
-        // if available.
-        p.getTextWidths(text, mCharWidths);
-        String fragment = text;
-        float top = rf.top + mEventTextAscent + topMargin;
-        int start = 0;
-
-        // Leave one pixel extra space at the bottom
-        while (start < len && height >= (lineHeight + 1)) {
-            boolean lastLine = (height < 2 * lineHeight + 1);
-            // Skip leading spaces at the beginning of each line
-            do {
-                char c = text.charAt(start);
-                if (c != ' ') break;
-                start += 1;
-            } while (start < len);
-
-            float sum = 0;
-            int end = start;
-            for (int ii = start; ii < len; ii++) {
-                char c = text.charAt(ii);
-
-                // If we found the end of a word, then remember the ending
-                // position.
-                if (c == ' ') {
-                    end = ii;
-                }
-                sum += mCharWidths[ii];
-                // If adding this character would exceed the width and this
-                // isn't the last line, then break the line at the previous
-                // word.  If there was no previous word, then break this word.
-                if (sum > width) {
-                    if (end > start && !lastLine) {
-                        // There was a previous word on this line.
-                        fragment = text.substring(start, end);
-                        start = end;
-                        break;
-                    }
-
-                    // This is the only word and it is too long to fit on
-                    // the line (or this is the last line), so take as many
-                    // characters of this word as will fit.
-                    fragment = text.substring(start, ii);
-                    start = ii;
-                    break;
-                }
-            }
-
-            // If sum <= width, then we can fit the rest of the text on
-            // this line.
-            if (sum <= width) {
-                fragment = text.substring(start, len);
-                start = len;
-            }
-
-            canvas.drawText(fragment, rf.left + 1, top, p);
-
-            top += lineHeight;
-            height -= lineHeight;
-        }
-    }
-
-    private void updateEventDetails() {
-        if (mSelectedEvent == null || mSelectionMode == SELECTION_HIDDEN
-                || mSelectionMode == SELECTION_LONGPRESS) {
-            mPopup.dismiss();
-            return;
-        }
-        if (mLastPopupEventID == mSelectedEvent.id) {
-            return;
-        }
-
-        mLastPopupEventID = mSelectedEvent.id;
-
-        // Remove any outstanding callbacks to dismiss the popup.
-        getHandler().removeCallbacks(mDismissPopup);
-
-        Event event = mSelectedEvent;
-        TextView titleView = (TextView) mPopupView.findViewById(R.id.event_title);
-        titleView.setText(event.title);
-
-        ImageView imageView = (ImageView) mPopupView.findViewById(R.id.reminder_icon);
-        imageView.setVisibility(event.hasAlarm ? View.VISIBLE : View.GONE);
-
-        imageView = (ImageView) mPopupView.findViewById(R.id.repeat_icon);
-        imageView.setVisibility(event.isRepeating ? View.VISIBLE : View.GONE);
-
-        int flags;
-        if (event.allDay) {
-            flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE |
-                    DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
-        } else {
-            flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE
-                    | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL
-                    | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-        }
-        if (DateFormat.is24HourFormat(mParentActivity)) {
-            flags |= DateUtils.FORMAT_24HOUR;
-        }
-        String timeRange = Utils.formatDateRange(mParentActivity,
-                event.startMillis, event.endMillis, flags);
-        TextView timeView = (TextView) mPopupView.findViewById(R.id.time);
-        timeView.setText(timeRange);
-
-        TextView whereView = (TextView) mPopupView.findViewById(R.id.where);
-        final boolean empty = TextUtils.isEmpty(event.location);
-        whereView.setVisibility(empty ? View.GONE : View.VISIBLE);
-        if (!empty) whereView.setText(event.location);
-
-        mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, mHoursWidth, 5);
-        postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
-
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-    }
-
-    // The following routines are called from the parent activity when certain
-    // touch events occur.
-
-    void doDown(MotionEvent ev) {
-        mTouchMode = TOUCH_MODE_DOWN;
-        mViewStartX = 0;
-        mOnFlingCalled = false;
-        getHandler().removeCallbacks(mContinueScroll);
-    }
-
-    void doSingleTapUp(MotionEvent ev) {
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-        int selectedDay = mSelectionDay;
-        int selectedHour = mSelectionHour;
-
-        boolean validPosition = setSelectionFromPosition(x, y);
-        if (!validPosition) {
-            // return if the touch wasn't on an area of concern
-            return;
-        }
-
-        mSelectionMode = SELECTION_SELECTED;
-        mRedrawScreen = true;
-        invalidate();
-
-        boolean launchNewView = false;
-        if (mSelectedEvent != null) {
-            // If the tap is on an event, launch the "View event" view
-            launchNewView = true;
-        } else if (mSelectedEvent == null && selectedDay == mSelectionDay
-                && selectedHour == mSelectionHour) {
-            // If the tap is on an already selected hour slot,
-            // then launch the Day/Agenda view. Otherwise, just select the hour
-            // slot.
-            launchNewView = true;
-        }
-
-        if (launchNewView) {
-            switchViews(false /* not the trackball */);
-        }
-    }
-
-    void doLongPress(MotionEvent ev) {
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        boolean validPosition = setSelectionFromPosition(x, y);
-        if (!validPosition) {
-            // return if the touch wasn't on an area of concern
-            return;
-        }
-
-        mSelectionMode = SELECTION_LONGPRESS;
-        mRedrawScreen = true;
-        invalidate();
-        performLongClick();
-    }
-
-    void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
-        // Use the distance from the current point to the initial touch instead
-        // of deltaX and deltaY to avoid accumulating floating-point rounding
-        // errors.  Also, we don't need floats, we can use ints.
-        int distanceX = (int) e1.getX() - (int) e2.getX();
-        int distanceY = (int) e1.getY() - (int) e2.getY();
-
-        // If we haven't figured out the predominant scroll direction yet,
-        // then do it now.
-        if (mTouchMode == TOUCH_MODE_DOWN) {
-            int absDistanceX = Math.abs(distanceX);
-            int absDistanceY = Math.abs(distanceY);
-            mScrollStartY = mViewStartY;
-            mPreviousDistanceX = 0;
-            mPreviousDirection = 0;
-
-            // If the x distance is at least twice the y distance, then lock
-            // the scroll horizontally.  Otherwise scroll vertically.
-            if (absDistanceX >= 2 * absDistanceY) {
-                mTouchMode = TOUCH_MODE_HSCROLL;
-                mViewStartX = distanceX;
-                initNextView(-mViewStartX);
-            } else {
-                mTouchMode = TOUCH_MODE_VSCROLL;
-            }
-        } else if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-            // We are already scrolling horizontally, so check if we
-            // changed the direction of scrolling so that the other week
-            // is now visible.
-            mViewStartX = distanceX;
-            if (distanceX != 0) {
-                int direction = (distanceX > 0) ? 1 : -1;
-                if (direction != mPreviousDirection) {
-                    // The user has switched the direction of scrolling
-                    // so re-init the next view
-                    initNextView(-mViewStartX);
-                    mPreviousDirection = direction;
-                }
-            }
-
-            // If we have moved at least the HORIZONTAL_SCROLL_THRESHOLD,
-            // then change the title to the new day (or week), but only
-            // if we haven't already changed the title.
-            if (distanceX >= HORIZONTAL_SCROLL_THRESHOLD) {
-                if (mPreviousDistanceX < HORIZONTAL_SCROLL_THRESHOLD) {
-                    CalendarView view = mParentActivity.getNextView();
-                    mTitleTextView.setText(view.mDateRange);
-                }
-            } else if (distanceX <= -HORIZONTAL_SCROLL_THRESHOLD) {
-                if (mPreviousDistanceX > -HORIZONTAL_SCROLL_THRESHOLD) {
-                    CalendarView view = mParentActivity.getNextView();
-                    mTitleTextView.setText(view.mDateRange);
-                }
-            } else {
-                if (mPreviousDistanceX >= HORIZONTAL_SCROLL_THRESHOLD
-                        || mPreviousDistanceX <= -HORIZONTAL_SCROLL_THRESHOLD) {
-                    mTitleTextView.setText(mDateRange);
-                }
-            }
-            mPreviousDistanceX = distanceX;
-        }
-
-        if ((mTouchMode & TOUCH_MODE_VSCROLL) != 0) {
-            mViewStartY = mScrollStartY + distanceY;
-            if (mViewStartY < 0) {
-                mViewStartY = 0;
-            } else if (mViewStartY > mMaxViewStartY) {
-                mViewStartY = mMaxViewStartY;
-            }
-            computeFirstHour();
-        }
-
-        mScrolling = true;
-
-        if (mSelectionMode != SELECTION_HIDDEN) {
-            mSelectionMode = SELECTION_HIDDEN;
-            mRedrawScreen = true;
-        }
-        invalidate();
-    }
-
-    void doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        mTouchMode = TOUCH_MODE_INITIAL_STATE;
-        mSelectionMode = SELECTION_HIDDEN;
-        mOnFlingCalled = true;
-        int deltaX = (int) e2.getX() - (int) e1.getX();
-        int distanceX = Math.abs(deltaX);
-        int deltaY = (int) e2.getY() - (int) e1.getY();
-        int distanceY = Math.abs(deltaY);
-
-        if ((distanceX >= HORIZONTAL_SCROLL_THRESHOLD) && (distanceX > distanceY)) {
-            boolean switchForward = initNextView(deltaX);
-            CalendarView view = mParentActivity.getNextView();
-            mTitleTextView.setText(view.mDateRange);
-            mParentActivity.switchViews(switchForward, mViewStartX, mViewWidth);
-            mViewStartX = 0;
-            return;
-        }
-
-        // Continue scrolling vertically
-        mContinueScroll.init((int) velocityY / 20);
-        post(mContinueScroll);
-    }
-
-    private boolean initNextView(int deltaX) {
-        // Change the view to the previous day or week
-        CalendarView view = mParentActivity.getNextView();
-        Time date = view.mBaseDate;
-        date.set(mBaseDate);
-        boolean switchForward;
-        if (deltaX > 0) {
-            date.monthDay -= mNumDays;
-            view.mSelectionDay = mSelectionDay - mNumDays;
-            switchForward = false;
-        } else {
-            date.monthDay += mNumDays;
-            view.mSelectionDay = mSelectionDay + mNumDays;
-            switchForward = true;
-        }
-        date.normalize(true /* ignore isDst */);
-        initView(view);
-        view.layout(getLeft(), getTop(), getRight(), getBottom());
-        view.reloadEvents();
-        return switchForward;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        int action = ev.getAction();
-
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            mParentActivity.mGestureDetector.onTouchEvent(ev);
-            return true;
-
-        case MotionEvent.ACTION_MOVE:
-            mParentActivity.mGestureDetector.onTouchEvent(ev);
-            return true;
-
-        case MotionEvent.ACTION_UP:
-            mParentActivity.mGestureDetector.onTouchEvent(ev);
-            if (mOnFlingCalled) {
-                return true;
-            }
-            if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
-                mTouchMode = TOUCH_MODE_INITIAL_STATE;
-                if (Math.abs(mViewStartX) > HORIZONTAL_SCROLL_THRESHOLD) {
-                    // The user has gone beyond the threshold so switch views
-                    mParentActivity.switchViews(mViewStartX > 0, mViewStartX, mViewWidth);
-                    mViewStartX = 0;
-                    return true;
-                } else {
-                    // Not beyond the threshold so invalidate which will cause
-                    // the view to snap back.  Also call recalc() to ensure
-                    // that we have the correct starting date and title.
-                    recalc();
-                    mTitleTextView.setText(mDateRange);
-                    invalidate();
-                    mViewStartX = 0;
-                }
-            }
-
-            // If we were scrolling, then reset the selected hour so that it
-            // is visible.
-            if (mScrolling) {
-                mScrolling = false;
-                resetSelectedHour();
-                mRedrawScreen = true;
-                invalidate();
-            }
-            return true;
-
-        // This case isn't expected to happen.
-        case MotionEvent.ACTION_CANCEL:
-            mParentActivity.mGestureDetector.onTouchEvent(ev);
-            mScrolling = false;
-            resetSelectedHour();
-            return true;
-
-        default:
-            if (mParentActivity.mGestureDetector.onTouchEvent(ev)) {
-                return true;
-            }
-            return super.onTouchEvent(ev);
-        }
-    }
-
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        MenuItem item;
-
-        // If the trackball is held down, then the context menu pops up and
-        // we never get onKeyUp() for the long-press.  So check for it here
-        // and change the selection to the long-press state.
-        if (mSelectionMode != SELECTION_LONGPRESS) {
-            mSelectionMode = SELECTION_LONGPRESS;
-            mRedrawScreen = true;
-            invalidate();
-        }
-
-        final long startMillis = getSelectedTimeInMillis();
-        int flags = DateUtils.FORMAT_SHOW_TIME
-                | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
-                | DateUtils.FORMAT_SHOW_WEEKDAY;
-        final String title = Utils.formatDateRange(mParentActivity, startMillis, startMillis,
-                flags);
-        menu.setHeaderTitle(title);
-
-        int numSelectedEvents = mSelectedEvents.size();
-        if (mNumDays == 1) {
-            // Day view.
-
-            // If there is a selected event, then allow it to be viewed and
-            // edited.
-            if (numSelectedEvents >= 1) {
-                item = menu.add(0, MenuHelper.MENU_EVENT_VIEW, 0, R.string.event_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_info_details);
-
-                int accessLevel = getEventAccessLevel(mParentActivity, mSelectedEvent);
-                if (accessLevel == ACCESS_LEVEL_EDIT) {
-                    item = menu.add(0, MenuHelper.MENU_EVENT_EDIT, 0, R.string.event_edit);
-                    item.setOnMenuItemClickListener(mContextMenuHandler);
-                    item.setIcon(android.R.drawable.ic_menu_edit);
-                    item.setAlphabeticShortcut('e');
-                }
-
-                if (accessLevel >= ACCESS_LEVEL_DELETE) {
-                    item = menu.add(0, MenuHelper.MENU_EVENT_DELETE, 0, R.string.event_delete);
-                    item.setOnMenuItemClickListener(mContextMenuHandler);
-                    item.setIcon(android.R.drawable.ic_menu_delete);
-                }
-
-                item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_add);
-                item.setAlphabeticShortcut('n');
-            } else {
-                // Otherwise, if the user long-pressed on a blank hour, allow
-                // them to create an event.  They can also do this by tapping.
-                item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_add);
-                item.setAlphabeticShortcut('n');
-            }
-        } else {
-            // Week view.
-
-            // If there is a selected event, then allow it to be viewed and
-            // edited.
-            if (numSelectedEvents >= 1) {
-                item = menu.add(0, MenuHelper.MENU_EVENT_VIEW, 0, R.string.event_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_info_details);
-
-                int accessLevel = getEventAccessLevel(mParentActivity, mSelectedEvent);
-                if (accessLevel == ACCESS_LEVEL_EDIT) {
-                    item = menu.add(0, MenuHelper.MENU_EVENT_EDIT, 0, R.string.event_edit);
-                    item.setOnMenuItemClickListener(mContextMenuHandler);
-                    item.setIcon(android.R.drawable.ic_menu_edit);
-                    item.setAlphabeticShortcut('e');
-                }
-
-                if (accessLevel >= ACCESS_LEVEL_DELETE) {
-                    item = menu.add(0, MenuHelper.MENU_EVENT_DELETE, 0, R.string.event_delete);
-                    item.setOnMenuItemClickListener(mContextMenuHandler);
-                    item.setIcon(android.R.drawable.ic_menu_delete);
-                }
-
-                item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_add);
-                item.setAlphabeticShortcut('n');
-
-                item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_day);
-                item.setAlphabeticShortcut('d');
-
-                item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_agenda);
-                item.setAlphabeticShortcut('a');
-            } else {
-                // No events are selected
-                item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_add);
-                item.setAlphabeticShortcut('n');
-
-                item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_day);
-                item.setAlphabeticShortcut('d');
-
-                item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
-                item.setOnMenuItemClickListener(mContextMenuHandler);
-                item.setIcon(android.R.drawable.ic_menu_agenda);
-                item.setAlphabeticShortcut('a');
-            }
-        }
-
-        mPopup.dismiss();
-    }
-
-    private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
-        public boolean onMenuItemClick(MenuItem item) {
-            switch (item.getItemId()) {
-                case MenuHelper.MENU_EVENT_VIEW: {
-                    if (mSelectedEvent != null) {
-                        long id = mSelectedEvent.id;
-                        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, id);
-                        Intent intent = new Intent(Intent.ACTION_VIEW);
-                        intent.setData(eventUri);
-                        intent.setClassName(mParentActivity, EventInfoActivity.class.getName());
-                        intent.putExtra(EVENT_BEGIN_TIME, mSelectedEvent.startMillis);
-                        intent.putExtra(EVENT_END_TIME, mSelectedEvent.endMillis);
-                        mParentActivity.startActivity(intent);
-                    }
-                    break;
-                }
-                case MenuHelper.MENU_EVENT_EDIT: {
-                    if (mSelectedEvent != null) {
-                        long id = mSelectedEvent.id;
-                        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, id);
-                        Intent intent = new Intent(Intent.ACTION_EDIT);
-                        intent.setData(eventUri);
-                        intent.setClassName(mParentActivity, EditEvent.class.getName());
-                        intent.putExtra(EVENT_BEGIN_TIME, mSelectedEvent.startMillis);
-                        intent.putExtra(EVENT_END_TIME, mSelectedEvent.endMillis);
-                        mParentActivity.startActivity(intent);
-                    }
-                    break;
-                }
-                case MenuHelper.MENU_DAY: {
-                    long startMillis = getSelectedTimeInMillis();
-                    Utils.startActivity(mParentActivity, DayActivity.class.getName(), startMillis);
-                    break;
-                }
-                case MenuHelper.MENU_AGENDA: {
-                    long startMillis = getSelectedTimeInMillis();
-                    Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
-                    break;
-                }
-                case MenuHelper.MENU_EVENT_CREATE: {
-                    long startMillis = getSelectedTimeInMillis();
-                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    intent.setClassName(mParentActivity, EditEvent.class.getName());
-                    intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-                    intent.putExtra(EVENT_END_TIME, endMillis);
-                    intent.putExtra(EditEvent.EVENT_ALL_DAY, mSelectionAllDay);
-                    mParentActivity.startActivity(intent);
-                    break;
-                }
-                case MenuHelper.MENU_EVENT_DELETE: {
-                    if (mSelectedEvent != null) {
-                        Event selectedEvent = mSelectedEvent;
-                        long begin = selectedEvent.startMillis;
-                        long end = selectedEvent.endMillis;
-                        long id = selectedEvent.id;
-                        mDeleteEventHelper.delete(begin, end, id, -1);
-                    }
-                    break;
-                }
-                default: {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private static int getEventAccessLevel(Context context, Event e) {
-        ContentResolver cr = context.getContentResolver();
-
-        int visibility = Calendars.NO_ACCESS;
-        int relationship = Attendees.RELATIONSHIP_ORGANIZER;
-
-        // Get the calendar id for this event
-        Cursor cursor = cr.query(ContentUris.withAppendedId(Events.CONTENT_URI, e.id),
-                new String[] { Events.CALENDAR_ID },
-                null /* selection */,
-                null /* selectionArgs */,
-                null /* sort */);
-
-        if (cursor == null) {
-            return ACCESS_LEVEL_NONE;
-        }
-
-        if (cursor.getCount() == 0) {
-            cursor.close();
-            return ACCESS_LEVEL_NONE;
-        }
-
-        cursor.moveToFirst();
-        long calId = cursor.getLong(0);
-        cursor.close();
-
-        Uri uri = Calendars.CONTENT_URI;
-        String where = String.format(CALENDARS_WHERE, calId);
-        cursor = cr.query(uri, CALENDARS_PROJECTION, where, null, null);
-
-        String calendarOwnerAccount = null;
-        if (cursor != null) {
-            cursor.moveToFirst();
-            visibility = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL);
-            calendarOwnerAccount = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-            cursor.close();
-        }
-
-        if (visibility < Calendars.CONTRIBUTOR_ACCESS) {
-            return ACCESS_LEVEL_NONE;
-        }
-
-        if (e.guestsCanModify) {
-            return ACCESS_LEVEL_EDIT;
-        }
-
-        if (!TextUtils.isEmpty(calendarOwnerAccount) &&
-                calendarOwnerAccount.equalsIgnoreCase(e.organizer)) {
-            return ACCESS_LEVEL_EDIT;
-        }
-
-        return ACCESS_LEVEL_DELETE;
-    }
-
-    /**
-     * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
-     * If the touch position is not within the displayed grid, then this
-     * method returns false.
-     *
-     * @param x the x position of the touch
-     * @param y the y position of the touch
-     * @return true if the touch position is valid
-     */
-    private boolean setSelectionFromPosition(int x, int y) {
-        if (x < mHoursWidth) {
-            return false;
-        }
-
-        int day = (x - mHoursWidth) / (mCellWidth + DAY_GAP);
-        if (day >= mNumDays) {
-            day = mNumDays - 1;
-        }
-        day += mFirstJulianDay;
-        int hour;
-        if (y < mFirstCell + mFirstHourOffset) {
-            mSelectionAllDay = true;
-        } else {
-            hour = (y - mFirstCell - mFirstHourOffset) / (mCellHeight + HOUR_GAP);
-            hour += mFirstHour;
-            mSelectionHour = hour;
-            mSelectionAllDay = false;
-        }
-        mSelectionDay = day;
-        findSelectedEvent(x, y);
-//        Log.i("Cal", "setSelectionFromPosition( " + x + ", " + y + " ) day: " + day
-//                + " hour: " + hour
-//                + " mFirstCell: " + mFirstCell + " mFirstHourOffset: " + mFirstHourOffset);
-//        if (mSelectedEvent != null) {
-//            Log.i("Cal", "  num events: " + mSelectedEvents.size() + " event: " + mSelectedEvent.title);
-//            for (Event ev : mSelectedEvents) {
-//                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
-//                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-//                String timeRange = Utils.formatDateRange(mParentActivity,
-//                        ev.startMillis, ev.endMillis, flags);
-//
-//                Log.i("Cal", "  " + timeRange + " " + ev.title);
-//            }
-//        }
-        return true;
-    }
-
-    private void findSelectedEvent(int x, int y) {
-        int date = mSelectionDay;
-        int cellWidth = mCellWidth;
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        int left = mHoursWidth + (mSelectionDay - mFirstJulianDay) * (cellWidth + DAY_GAP);
-        int top = 0;
-        mSelectedEvent = null;
-
-        mSelectedEvents.clear();
-        if (mSelectionAllDay) {
-            float yDistance;
-            float minYdistance = 10000.0f;  // any large number
-            Event closestEvent = null;
-            float drawHeight = mAllDayHeight;
-            int yOffset = mBannerPlusMargin + ALLDAY_TOP_MARGIN;
-            for (int i = 0; i < numEvents; i++) {
-                Event event = events.get(i);
-                if (!event.allDay) {
-                    continue;
-                }
-
-                if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
-                    float numRectangles = event.getMaxColumns();
-                    float height = drawHeight / numRectangles;
-                    if (height > MAX_ALLDAY_EVENT_HEIGHT) {
-                        height = MAX_ALLDAY_EVENT_HEIGHT;
-                    }
-                    float eventTop = yOffset + height * event.getColumn();
-                    float eventBottom = eventTop + height;
-                    if (eventTop < y && eventBottom > y) {
-                        // If the touch is inside the event rectangle, then
-                        // add the event.
-                        mSelectedEvents.add(event);
-                        closestEvent = event;
-                        break;
-                    } else {
-                        // Find the closest event
-                        if (eventTop >= y) {
-                            yDistance = eventTop - y;
-                        } else {
-                            yDistance = y - eventBottom;
-                        }
-                        if (yDistance < minYdistance) {
-                            minYdistance = yDistance;
-                            closestEvent = event;
-                        }
-                    }
-                }
-            }
-            mSelectedEvent = closestEvent;
-            return;
-        }
-
-        // Adjust y for the scrollable bitmap
-        y += mViewStartY - mFirstCell;
-
-        // Use a region around (x,y) for the selection region
-        Rect region = mRect;
-        region.left = x - 10;
-        region.right = x + 10;
-        region.top = y - 10;
-        region.bottom = y + 10;
-
-        EventGeometry geometry = mEventGeometry;
-
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            // Compute the event rectangle.
-            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
-                continue;
-            }
-
-            // If the event intersects the selection region, then add it to
-            // mSelectedEvents.
-            if (geometry.eventIntersectsSelection(event, region)) {
-                mSelectedEvents.add(event);
-            }
-        }
-
-        // If there are any events in the selected region, then assign the
-        // closest one to mSelectedEvent.
-        if (mSelectedEvents.size() > 0) {
-            int len = mSelectedEvents.size();
-            Event closestEvent = null;
-            float minDist = mViewWidth + mViewHeight;  // some large distance
-            for (int index = 0; index < len; index++) {
-                Event ev = mSelectedEvents.get(index);
-                float dist = geometry.pointToEvent(x, y, ev);
-                if (dist < minDist) {
-                    minDist = dist;
-                    closestEvent = ev;
-                }
-            }
-            mSelectedEvent = closestEvent;
-
-            // Keep the selected hour and day consistent with the selected
-            // event.  They could be different if we touched on an empty hour
-            // slot very close to an event in the previous hour slot.  In
-            // that case we will select the nearby event.
-            int startDay = mSelectedEvent.startDay;
-            int endDay = mSelectedEvent.endDay;
-            if (mSelectionDay < startDay) {
-                mSelectionDay = startDay;
-            } else if (mSelectionDay > endDay) {
-                mSelectionDay = endDay;
-            }
-
-            int startHour = mSelectedEvent.startTime / 60;
-            int endHour;
-            if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
-                endHour = (mSelectedEvent.endTime - 1) / 60;
-            } else {
-                endHour = mSelectedEvent.endTime / 60;
-            }
-
-            if (mSelectionHour < startHour) {
-                mSelectionHour = startHour;
-            } else if (mSelectionHour > endHour) {
-                mSelectionHour = endHour;
-            }
-        }
-    }
-
-    // Encapsulates the code to continue the scrolling after the
-    // finger is lifted.  Instead of stopping the scroll immediately,
-    // the scroll continues to "free spin" and gradually slows down.
-    private class ContinueScroll implements Runnable {
-        int mSignDeltaY;
-        int mAbsDeltaY;
-        float mFloatDeltaY;
-        long mFreeSpinTime;
-        private static final float FRICTION_COEF = 0.7F;
-        private static final long FREE_SPIN_MILLIS = 180;
-        private static final int MAX_DELTA = 60;
-        private static final int SCROLL_REPEAT_INTERVAL = 30;
-
-        public void init(int deltaY) {
-            mSignDeltaY = 0;
-            if (deltaY > 0) {
-                mSignDeltaY = 1;
-            } else if (deltaY < 0) {
-                mSignDeltaY = -1;
-            }
-            mAbsDeltaY = Math.abs(deltaY);
-
-            // Limit the maximum speed
-            if (mAbsDeltaY > MAX_DELTA) {
-                mAbsDeltaY = MAX_DELTA;
-            }
-            mFloatDeltaY = mAbsDeltaY;
-            mFreeSpinTime = System.currentTimeMillis() + FREE_SPIN_MILLIS;
-//            Log.i("Cal", "init scroll: mAbsDeltaY: " + mAbsDeltaY
-//                    + " mViewStartY: " + mViewStartY);
-        }
-
-        public void run() {
-            long time = System.currentTimeMillis();
-
-            // Start out with a frictionless "free spin"
-            if (time > mFreeSpinTime) {
-                // If the delta is small, then apply a fixed deceleration.
-                // Otherwise
-                if (mAbsDeltaY <= 10) {
-                    mAbsDeltaY -= 2;
-                } else {
-                    mFloatDeltaY *= FRICTION_COEF;
-                    mAbsDeltaY = (int) mFloatDeltaY;
-                }
-
-                if (mAbsDeltaY < 0) {
-                    mAbsDeltaY = 0;
-                }
-            }
-
-            if (mSignDeltaY == 1) {
-                mViewStartY -= mAbsDeltaY;
-            } else {
-                mViewStartY += mAbsDeltaY;
-            }
-//            Log.i("Cal", "  scroll: mAbsDeltaY: " + mAbsDeltaY
-//                    + " mViewStartY: " + mViewStartY);
-
-            if (mViewStartY < 0) {
-                mViewStartY = 0;
-                mAbsDeltaY = 0;
-            } else if (mViewStartY > mMaxViewStartY) {
-                mViewStartY = mMaxViewStartY;
-                mAbsDeltaY = 0;
-            }
-
-            computeFirstHour();
-
-            if (mAbsDeltaY > 0) {
-                postDelayed(this, SCROLL_REPEAT_INTERVAL);
-            } else {
-                // Done scrolling.
-                mScrolling = false;
-                resetSelectedHour();
-                mRedrawScreen = true;
-            }
-
-            invalidate();
-        }
-    }
-
-    /**
-     * Cleanup the pop-up and timers.
-     */
-    public void cleanup() {
-        // Protect against null-pointer exceptions
-        if (mPopup != null) {
-            mPopup.dismiss();
-        }
-        mLastPopupEventID = INVALID_EVENT_ID;
-        Handler handler = getHandler();
-        if (handler != null) {
-            handler.removeCallbacks(mDismissPopup);
-            handler.removeCallbacks(mUpdateCurrentTime);
-        }
-
-        // Turn off redraw
-        mRemeasure = false;
-        mRedrawScreen = false;
-
-        // clear the cached values for accessibility support
-        mPrevSelectionDay = 0;
-        mPrevSelectionHour = 0;
-        mPrevTitleTextViewText = null;
-    }
-
-    /**
-     * Restart the update timer
-     */
-    public void updateView() {
-        mUpdateTZ.run();
-        post(mUpdateCurrentTime);
-    }
-
-    @Override protected void onDetachedFromWindow() {
-        cleanup();
-        if (mBitmap != null) {
-            mBitmap.recycle();
-            mBitmap = null;
-        }
-        super.onDetachedFromWindow();
-    }
-
-    class DismissPopup implements Runnable {
-        public void run() {
-            // Protect against null-pointer exceptions
-            if (mPopup != null) {
-                mPopup.dismiss();
-            }
-        }
-    }
-
-    class UpdateCurrentTime implements Runnable {
-        public void run() {
-            long currentTime = System.currentTimeMillis();
-            mCurrentTime.set(currentTime);
-            //% causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
-            postDelayed(mUpdateCurrentTime,
-                    UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
-            mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
-            mRedrawScreen = true;
-            invalidate();
-        }
-    }
-}
diff --git a/src/com/android/calendar/ContactsAsyncHelper.java b/src/com/android/calendar/ContactsAsyncHelper.java
index dfccd37..61459de 100644
--- a/src/com/android/calendar/ContactsAsyncHelper.java
+++ b/src/com/android/calendar/ContactsAsyncHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.calendar;
 
+import com.android.calendar.event.EditEventHelper.AttendeeItem;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -53,6 +55,7 @@
 
     // constants
     private static final int EVENT_LOAD_IMAGE = 1;
+    private static final int EVENT_LOAD_DRAWABLE = 2;
     private static final int DEFAULT_TOKEN = -1;
 
     // static objects
@@ -69,6 +72,8 @@
         public Uri uri;
         public int defaultResource;
         public Object result;
+        public AttendeeItem item;
+        public Runnable callback;
     }
 
     /**
@@ -85,6 +90,7 @@
             WorkerArgs args = (WorkerArgs) msg.obj;
 
             switch (msg.arg1) {
+                case EVENT_LOAD_DRAWABLE:
                 case EVENT_LOAD_IMAGE:
                     InputStream inputStream = null;
                     try {
@@ -176,6 +182,43 @@
     }
 
     /**
+     * Start an image load, attach the result to the specified CallerInfo object.
+     * Note, when the query is started, we make the ImageView INVISIBLE if the
+     * placeholderImageResource value is -1.  When we're given a valid (!= -1)
+     * placeholderImageResource value, we make sure the image is visible.
+     */
+    public static final void retrieveContactPhotoAsync(Context context,
+            AttendeeItem item, Runnable run, Uri person) {
+
+        // in case the source caller info is null, the URI will be null as well.
+        // just return as there's nothing to do.
+        if (person == null) {
+            return;
+        }
+
+        // Added additional Cookie field in the callee to handle arguments
+        // sent to the callback function.
+
+        // setup arguments
+        WorkerArgs args = new WorkerArgs();
+        args.context = context;
+        args.item = item;
+        args.uri = person;
+        args.callback = run;
+
+        // setup message arguments
+        Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN);
+        msg.arg1 = EVENT_LOAD_DRAWABLE;
+        msg.obj = args;
+
+        if (DBG) Log.d(LOG_TAG, "Begin loading drawable: " + args.uri);
+
+
+        // notify the thread to begin working
+        sThreadHandler.sendMessage(msg);
+    }
+
+    /**
      * Called when loading is done.
      */
     @Override
@@ -183,19 +226,24 @@
         WorkerArgs args = (WorkerArgs) msg.obj;
         switch (msg.arg1) {
             case EVENT_LOAD_IMAGE:
-                boolean imagePresent = false;
-
                 // if the image has been loaded then display it, otherwise set default.
                 // in either case, make sure the image is visible.
                 if (args.result != null) {
                     args.view.setVisibility(View.VISIBLE);
                     args.view.setImageDrawable((Drawable) args.result);
-                    imagePresent = true;
                 } else if (args.defaultResource != -1) {
                     args.view.setVisibility(View.VISIBLE);
                     args.view.setImageResource(args.defaultResource);
                 }
                 break;
+            case EVENT_LOAD_DRAWABLE:
+                if (args.result != null) {
+                    args.item.mBadge = (Drawable) args.result;
+                    if (args.callback != null) {
+                        args.callback.run();
+                    }
+                }
+                break;
             default:
         }
     }
diff --git a/src/com/android/calendar/DayActivity.java b/src/com/android/calendar/DayActivity.java
deleted file mode 100644
index df226f6..0000000
--- a/src/com/android/calendar/DayActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2006 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.calendar;
-
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.ProgressBar;
-import android.widget.ViewSwitcher;
-
-public class DayActivity extends CalendarActivity implements ViewSwitcher.ViewFactory {
-    /**
-     * The view id used for all the views we create. It's OK to have all child
-     * views have the same ID. This ID is used to pick which view receives
-     * focus when a view hierarchy is saved / restore
-     */
-    private static final int VIEW_ID = 1;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setContentView(R.layout.day_activity);
-
-        mSelectedDay = Utils.timeFromIntent(getIntent());
-        mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
-        mViewSwitcher.setFactory(this);
-        mViewSwitcher.getCurrentView().requestFocus();
-        mProgressBar = (ProgressBar) findViewById(R.id.progress_circular);
-    }
-
-    public View makeView() {
-        DayView view = new DayView(this);
-        view.setId(VIEW_ID);
-        view.setLayoutParams(new ViewSwitcher.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        view.setSelectedDay(mSelectedDay);
-        return view;
-    }
-    
-    @Override
-    protected void onPause() {
-        super.onPause();
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        mSelectedDay = view.getSelectedDay();
-    }
-    
-    @Override
-    protected void onResume() {
-        super.onResume();
-        // Record Day View as the (new) default detailed view.
-        Utils.setDefaultView(this, CalendarApplication.DAY_VIEW_ID);
-    }
-}
diff --git a/src/com/android/calendar/DayFragment.java b/src/com/android/calendar/DayFragment.java
new file mode 100644
index 0000000..655fb88
--- /dev/null
+++ b/src/com/android/calendar/DayFragment.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+
+import dalvik.system.VMRuntime;
+
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.format.Time;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ProgressBar;
+import android.widget.ViewSwitcher;
+import android.widget.ViewSwitcher.ViewFactory;
+
+/**
+ * This is the base class for Day and Week Activities.
+ */
+public class DayFragment extends Fragment implements CalendarController.EventHandler, ViewFactory {
+    /**
+     * The view id used for all the views we create. It's OK to have all child
+     * views have the same ID. This ID is used to pick which view receives
+     * focus when a view hierarchy is saved / restore
+     */
+    private static final int VIEW_ID = 1;
+
+    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
+
+    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+
+    protected ProgressBar mProgressBar;
+    protected ViewSwitcher mViewSwitcher;
+    protected Animation mInAnimationForward;
+    protected Animation mOutAnimationForward;
+    protected Animation mInAnimationBackward;
+    protected Animation mOutAnimationBackward;
+    EventLoader mEventLoader;
+
+    Time mSelectedDay = new Time();
+
+    /**
+     * Listens for intent broadcasts
+     */
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_TIME_CHANGED)
+                    || action.equals(Intent.ACTION_DATE_CHANGED)
+                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
+                eventsChanged();
+            }
+        }
+    };
+
+    // Create an observer so that we can update the views whenever a
+    // Calendar event changes.
+    private ContentObserver mObserver = new ContentObserver(new Handler())
+    {
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            eventsChanged();
+        }
+    };
+
+    private int mNumDays;
+
+    public DayFragment() {
+        mSelectedDay.setToNow();
+    }
+
+    public DayFragment(long timeMillis, int numOfDays) {
+        mNumDays = numOfDays;
+        if (timeMillis == 0) {
+            mSelectedDay.setToNow();
+        } else {
+            mSelectedDay.set(timeMillis);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
+        // TODO: We should restore the old heap size once the activity reaches the idle state
+        VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
+
+//        setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
+        Context context = getActivity();
+
+        mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in);
+        mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out);
+        mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in);
+        mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out);
+
+        mEventLoader = new EventLoader(context);
+    }
+
+//    @Override
+//    public void onRestoreInstanceState(Bundle savedInstanceState) {
+//        super.onRestoreInstanceState(savedInstanceState);
+//
+//        DayView view = (DayView) mViewSwitcher.getCurrentView();
+//        Time time = new Time();
+//        time.set(savedInstanceState.getLong(BUNDLE_KEY_RESTORE_TIME));
+//        view.setSelectedDay(time);
+//    }
+//
+//    @Override
+//    public void onNewIntent(Intent intent) {
+//        long timeMillis = Utils.timeFromIntentInMillis(intent);
+//        if (timeMillis > 0) {
+//            Time time = new Time();
+//            time.set(timeMillis);
+//            goTo(time, false);
+//        }
+//    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.day_activity, null);
+
+        mViewSwitcher = (ViewSwitcher) v.findViewById(R.id.switcher);
+        mViewSwitcher.setFactory(this);
+        mViewSwitcher.getCurrentView().requestFocus();
+
+        return v;
+    }
+
+    public View makeView() {
+        DayView view = new DayView(getActivity(), CalendarController
+                .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays);
+        view.setId(VIEW_ID);
+        view.setLayoutParams(new ViewSwitcher.LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        view.setSelectedDay(mSelectedDay);
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mEventLoader.startBackgroundThread();
+        eventsChanged();
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        view.updateIs24HourFormat();
+        view.restartCurrentTimeUpdates();
+
+        view = (DayView) mViewSwitcher.getNextView();
+        view.updateIs24HourFormat();
+
+        // Register for Intent broadcasts
+//        IntentFilter filter = new IntentFilter();
+//
+//        filter.addAction(Intent.ACTION_TIME_CHANGED);
+//        filter.addAction(Intent.ACTION_DATE_CHANGED);
+//        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+//        registerReceiver(mIntentReceiver, filter);
+
+//        mContentResolver.registerContentObserver(Calendar.Events.CONTENT_URI,
+//                true, mObserver);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTimeInMillis());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+//        mContentResolver.unregisterContentObserver(mObserver);
+//        unregisterReceiver(mIntentReceiver);
+
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        view.cleanup();
+        view = (DayView) mViewSwitcher.getNextView();
+        view.cleanup();
+        mEventLoader.stopBackgroundThread();
+    }
+
+    void startProgressSpinner() {
+        // start the progress spinner
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    void stopProgressSpinner() {
+        // stop the progress spinner
+        mProgressBar.setVisibility(View.GONE);
+    }
+
+    /* Navigator interface methods */
+    public void goTo(Time goToTime, boolean animate) {
+        if (mViewSwitcher == null) {
+            // The view hasn't been set yet. Just save the time and use it later.
+            mSelectedDay.set(goToTime);
+            return;
+        }
+
+        DayView currentView = (DayView) mViewSwitcher.getCurrentView();
+        Time selectedTime = currentView.getSelectedTime();
+
+        // Going to the same time
+        if (selectedTime.equals(goToTime)) {
+            return;
+        }
+
+        // How does goTo time compared to what's already displaying?
+        int diff = currentView.compareToVisibleTimeRange(goToTime);
+
+        if (diff == 0) {
+            // In visible range. No need to switch view
+            currentView.setSelectedDay(goToTime);
+        } else {
+            // Figure out which way to animate
+            if (animate) {
+                if (diff > 0) {
+                    mViewSwitcher.setInAnimation(mInAnimationForward);
+                    mViewSwitcher.setOutAnimation(mOutAnimationForward);
+                } else {
+                    mViewSwitcher.setInAnimation(mInAnimationBackward);
+                    mViewSwitcher.setOutAnimation(mOutAnimationBackward);
+                }
+            }
+
+            DayView next = (DayView) mViewSwitcher.getNextView();
+            next.setSelectedDay(goToTime);
+            next.reloadEvents();
+            mViewSwitcher.showNext();
+            next.requestFocus();
+        }
+    }
+
+    /**
+     * Returns the selected time in milliseconds. The milliseconds are measured
+     * in UTC milliseconds from the epoch and uniquely specifies any selectable
+     * time.
+     *
+     * @return the selected time in milliseconds
+     */
+    public long getSelectedTimeInMillis() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        return view.getSelectedTimeInMillis();
+    }
+
+    public long getSelectedTime() {
+        return getSelectedTimeInMillis();
+    }
+
+    public void goToToday() {
+        mSelectedDay.set(System.currentTimeMillis());
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        view.setSelectedDay(mSelectedDay);
+        view.reloadEvents();
+    }
+
+    public boolean getAllDay() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        return view.mSelectionAllDay;
+    }
+
+    public void eventsChanged() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        view.clearCachedEvents();
+        view.reloadEvents();
+    }
+
+    Event getSelectedEvent() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        return view.getSelectedEvent();
+    }
+
+    boolean isEventSelected() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        return view.isEventSelected();
+    }
+
+    Event getNewEvent() {
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        return view.getNewEvent();
+    }
+
+    public DayView getNextView() {
+        return (DayView) mViewSwitcher.getNextView();
+    }
+
+    public long getSupportedEventTypes() {
+        return EventType.GO_TO | EventType.EVENTS_CHANGED;
+    }
+
+    public void handleEvent(EventInfo msg) {
+        if (msg.eventType == EventType.GO_TO) {
+// TODO support a range of time
+// TODO support event_id
+// TODO figure out the animate bit
+// TODO support select message
+            goTo(msg.startTime, true);
+        } else if (msg.eventType == EventType.EVENTS_CHANGED) {
+            eventsChanged();
+        }
+    }
+}
diff --git a/src/com/android/calendar/DayView.java b/src/com/android/calendar/DayView.java
index a24b51b..4a040c9 100644
--- a/src/com/android/calendar/DayView.java
+++ b/src/com/android/calendar/DayView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2007 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.
@@ -16,18 +16,3332 @@
 
 package com.android.calendar;
 
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.CalendarController.ViewType;
 
-public class DayView extends CalendarView {
-    private static final int CELL_MARGIN = 10;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
 
-    public DayView(CalendarActivity activity) {
-        super(activity);
-        init();
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * View for multi-day view. So far only 1 and 7 day have been tested.
+ */
+public class DayView extends View
+        implements View.OnCreateContextMenuListener, View.OnClickListener {
+    private static String TAG = "DayView";
+
+    private static float mScale = 0; // Used for supporting different screen densities
+    private static final long INVALID_EVENT_ID = -1; //This is used for remembering a null event
+    private static final long ANIMATION_DURATION = 400;
+
+    private static final int MENU_AGENDA = 2;
+    private static final int MENU_DAY = 3;
+    private static final int MENU_EVENT_VIEW = 5;
+    private static final int MENU_EVENT_CREATE = 6;
+    private static final int MENU_EVENT_EDIT = 7;
+    private static final int MENU_EVENT_DELETE = 8;
+
+    private static int DEFAULT_CELL_HEIGHT = 52;
+
+    private boolean mOnFlingCalled;
+    /**
+     * ID of the last event which was displayed with the toast popup.
+     *
+     * This is used to prevent popping up multiple quick views for the same event, especially
+     * during calendar syncs. This becomes valid when an event is selected, either by default
+     * on starting calendar or by scrolling to an event. It becomes invalid when the user
+     * explicitly scrolls to an empty time slot, changes views, or deletes the event.
+     */
+    private long mLastPopupEventID;
+
+    protected Context mContext;
+
+    private static final String[] CALENDARS_PROJECTION = new String[] {
+        Calendars._ID,          // 0
+        Calendars.ACCESS_LEVEL, // 1
+        Calendars.OWNER_ACCOUNT, // 2
+    };
+    private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
+    private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
+    private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
+
+    private static final String[] ATTENDEES_PROJECTION = new String[] {
+        Attendees._ID,                      // 0
+        Attendees.ATTENDEE_RELATIONSHIP,    // 1
+    };
+    private static final int ATTENDEES_INDEX_RELATIONSHIP = 1;
+    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
+
+    private static float SMALL_ROUND_RADIUS = 3.0F;
+
+    private static final int FROM_NONE = 0;
+    private static final int FROM_ABOVE = 1;
+    private static final int FROM_BELOW = 2;
+    private static final int FROM_LEFT = 4;
+    private static final int FROM_RIGHT = 8;
+
+    private static final int ACCESS_LEVEL_NONE = 0;
+    private static final int ACCESS_LEVEL_DELETE = 1;
+    private static final int ACCESS_LEVEL_EDIT = 2;
+
+    private static int HORIZONTAL_SCROLL_THRESHOLD = 50;
+
+    private ContinueScroll mContinueScroll = new ContinueScroll();
+
+    static private class DayHeader{
+        int cell;
+        String dateString;
     }
 
-    private void init() {
-        mDrawTextInEventRect = true;
-        mNumDays = 1;
-        mEventGeometry.setCellMargin(CELL_MARGIN);
+    private DayHeader[] dayHeaders = new DayHeader[32];
+
+    // Make this visible within the package for more informative debugging
+    Time mBaseDate;
+    private Time mCurrentTime;
+    //Update the current time line every five minutes if the window is left open that long
+    private static final int UPDATE_CURRENT_TIME_DELAY = 300000;
+    private UpdateCurrentTime mUpdateCurrentTime = new UpdateCurrentTime();
+    private int mTodayJulianDay;
+
+    private Typeface mBold = Typeface.DEFAULT_BOLD;
+    private int mFirstJulianDay;
+    private int mLastJulianDay;
+
+    private int mMonthLength;
+    private int mFirstVisibleDate;
+    private int mFirstVisibleDayOfWeek;
+    private int[] mEarliestStartHour;    // indexed by the week day offset
+    private boolean[] mHasAllDayEvent;   // indexed by the week day offset
+
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            String tz = Utils.getTimeZone(mContext, this);
+            mBaseDate.timezone = tz;
+            mBaseDate.normalize(true);
+            mCurrentTime.switchTimezone(tz);
+            invalidate();
+        }
+    };
+
+    /**
+     * This variable helps to avoid unnecessarily reloading events by keeping
+     * track of the start millis parameter used for the most recent loading
+     * of events.  If the next reload matches this, then the events are not
+     * reloaded.  To force a reload, set this to zero (this is set to zero
+     * in the method clearCachedEvents()).
+     */
+    private long mLastReloadMillis;
+
+    private ArrayList<Event> mEvents = new ArrayList<Event>();
+    private int mSelectionDay;        // Julian day
+    private int mSelectionHour;
+
+    boolean mSelectionAllDay;
+
+    private int mCellWidth;
+
+    // Pre-allocate these objects and re-use them
+    private Rect mRect = new Rect();
+    private RectF mRectF = new RectF();
+    private Rect mSrcRect = new Rect();
+    private Rect mDestRect = new Rect();
+    private Paint mPaint = new Paint();
+    private Paint mPaintBorder = new Paint();
+    private Paint mEventTextPaint = new Paint();
+    private Paint mSelectionPaint = new Paint();
+    private Path mPath = new Path();
+
+    protected boolean mDrawTextInEventRect = true;
+    private int mFirstDayOfWeek; // First day of the week
+
+    private PopupWindow mPopup;
+    private View mPopupView;
+
+    // The number of milliseconds to show the popup window
+    private static final int POPUP_DISMISS_DELAY = 3000;
+    private DismissPopup mDismissPopup = new DismissPopup();
+
+    // For drawing to an off-screen Canvas
+    private Bitmap mBitmap;
+    private Canvas mCanvas;
+    private boolean mRemeasure = true;
+
+    private final EventLoader mEventLoader;
+    protected final EventGeometry mEventGeometry;
+
+    private static final int DAY_GAP = 1;
+    private static final int HOUR_GAP = 1;
+    private static int SINGLE_ALLDAY_HEIGHT = 20;
+    private static int MAX_ALLDAY_HEIGHT = 72;
+    private static int ALLDAY_TOP_MARGIN = 3;
+    private static int MAX_ALLDAY_EVENT_HEIGHT = 18;
+
+    /* The extra space to leave above the text in all-day events */
+    private static final int ALL_DAY_TEXT_TOP_MARGIN = 0;
+
+    /* The extra space to leave above the text in normal events */
+    private static final int NORMAL_TEXT_TOP_MARGIN = 2;
+
+    private static final int HOURS_LEFT_MARGIN = 2;
+    private static final int HOURS_RIGHT_MARGIN = 4;
+    private static final int HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
+
+    private static int CURRENT_TIME_LINE_HEIGHT = 2;
+    private static int CURRENT_TIME_LINE_BORDER_WIDTH = 1;
+    private static int CURRENT_TIME_MARKER_INNER_WIDTH = 6;
+    private static int CURRENT_TIME_MARKER_HEIGHT = 6;
+    private static int CURRENT_TIME_MARKER_WIDTH = 8;
+    private static int CURRENT_TIME_LINE_SIDE_BUFFER = 1;
+
+    /* package */ static final int MINUTES_PER_HOUR = 60;
+    /* package */ static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
+    /* package */ static final int MILLIS_PER_MINUTE = 60 * 1000;
+    /* package */ static final int MILLIS_PER_HOUR = (3600 * 1000);
+    /* package */ static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
+
+    private static int NORMAL_FONT_SIZE = 12;
+    private static int EVENT_TEXT_FONT_SIZE = 12;
+    private static int HOURS_FONT_SIZE = 12;
+    private static int AMPM_FONT_SIZE = 9;
+    private static int MIN_CELL_WIDTH_FOR_TEXT = 27;
+    private static final int MAX_EVENT_TEXT_LEN = 500;
+    private static float MIN_EVENT_HEIGHT = 15.0F;  // in pixels
+
+    private static int mSelectionColor;
+    private static int mPressedColor;
+    private static int mSelectedEventTextColor;
+    private static int mEventTextColor;
+    private static int mWeek_saturdayColor;
+    private static int mWeek_sundayColor;
+    private static int mCalendarDateBannerTextColor;
+    private static int mCalendarAllDayBackground;
+    private static int mCalendarAmPmLabel;
+    private static int mCalendarDateBannerBackground;
+    private static int mCalendarDateSelected;
+    private static int mCalendarGridAreaBackground;
+    private static int mCalendarGridAreaSelected;
+    private static int mCalendarGridLineHorizontalColor;
+    private static int mCalendarGridLineVerticalColor;
+    private static int mCalendarHourBackground;
+    private static int mCalendarHourLabel;
+    private static int mCalendarHourSelected;
+    private static int mCurrentTimeMarkerColor;
+    private static int mCurrentTimeMarkerBorderColor;
+
+    private int mViewStartX;
+    private int mViewStartY;
+    private int mMaxViewStartY;
+    private int mBitmapHeight;
+    private int mViewHeight;
+    private int mViewWidth;
+    private int mGridAreaHeight;
+    private int mCellHeight;
+    private int mScrollStartY;
+    private int mPreviousDirection;
+    private int mPreviousDistanceX;
+
+    private int mHoursTextHeight;
+    private int mEventTextAscent;
+    private int mEventTextHeight;
+    private int mAllDayHeight;
+    private int mBannerPlusMargin;
+    private int mMaxAllDayEvents;
+
+    protected int mNumDays = 7;
+    private int mNumHours = 10;
+    private int mHoursWidth;
+    private int mDateStrWidth;
+    private int mFirstCell;
+    private int mFirstHour = -1;
+    private int mFirstHourOffset;
+    private String[] mHourStrs;
+    private String[] mDayStrs;
+    private String[] mDayStrs2Letter;
+    private boolean mIs24HourFormat;
+
+    private float[] mCharWidths = new float[MAX_EVENT_TEXT_LEN];
+    private ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
+    private boolean mComputeSelectedEvents;
+    private Event mSelectedEvent;
+    private Event mPrevSelectedEvent;
+    private Rect mPrevBox = new Rect();
+    protected final Resources mResources;
+    private String mAmString;
+    private String mPmString;
+    private DeleteEventHelper mDeleteEventHelper;
+
+    private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
+
+    /**
+     * The initial state of the touch mode when we enter this view.
+     */
+    private static final int TOUCH_MODE_INITIAL_STATE = 0;
+
+    /**
+     * Indicates we just received the touch event and we are waiting to see if
+     * it is a tap or a scroll gesture.
+     */
+    private static final int TOUCH_MODE_DOWN = 1;
+
+    /**
+     * Indicates the touch gesture is a vertical scroll
+     */
+    private static final int TOUCH_MODE_VSCROLL = 0x20;
+
+    /**
+     * Indicates the touch gesture is a horizontal scroll
+     */
+    private static final int TOUCH_MODE_HSCROLL = 0x40;
+
+    private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
+
+    /**
+     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+     */
+    private static final int SELECTION_HIDDEN = 0;
+    private static final int SELECTION_PRESSED = 1;
+    private static final int SELECTION_SELECTED = 2;
+    private static final int SELECTION_LONGPRESS = 3;
+
+    private int mSelectionMode = SELECTION_HIDDEN;
+
+    private boolean mScrolling = false;
+
+    private String mDateRange;
+    private TextView mTitleTextView;
+    private CalendarController mController;
+    private ViewSwitcher mViewSwitcher;
+    private GestureDetector mGestureDetector;
+
+    public DayView(Context context, CalendarController controller,
+            ViewSwitcher viewSwitcher, EventLoader eventLoader, int numDays) {
+        super(context);
+        if (mScale == 0) {
+            mScale = getContext().getResources().getDisplayMetrics().density;
+            if (mScale != 1) {
+                SINGLE_ALLDAY_HEIGHT *= mScale;
+                MAX_ALLDAY_HEIGHT *= mScale;
+                ALLDAY_TOP_MARGIN *= mScale;
+                MAX_ALLDAY_EVENT_HEIGHT *= mScale;
+
+                NORMAL_FONT_SIZE *= mScale;
+                EVENT_TEXT_FONT_SIZE *= mScale;
+                HOURS_FONT_SIZE *= mScale;
+                AMPM_FONT_SIZE *= mScale;
+                MIN_CELL_WIDTH_FOR_TEXT *= mScale;
+                MIN_EVENT_HEIGHT *= mScale;
+
+                HORIZONTAL_SCROLL_THRESHOLD *= mScale;
+
+                CURRENT_TIME_MARKER_HEIGHT *= mScale;
+                CURRENT_TIME_MARKER_WIDTH *= mScale;
+                CURRENT_TIME_LINE_HEIGHT *= mScale;
+                CURRENT_TIME_LINE_BORDER_WIDTH *= mScale;
+                CURRENT_TIME_MARKER_INNER_WIDTH *= mScale;
+                CURRENT_TIME_LINE_SIDE_BUFFER *= mScale;
+
+                SMALL_ROUND_RADIUS *= mScale;
+                DEFAULT_CELL_HEIGHT *= mScale;
+            }
+        }
+
+        mResources = context.getResources();
+        mEventLoader = eventLoader;
+        mEventGeometry = new EventGeometry();
+        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
+        mEventGeometry.setHourGap(HOUR_GAP);
+        mContext = context;
+        mDeleteEventHelper = new DeleteEventHelper(context, null, false /* don't exit when done */);
+        mLastPopupEventID = INVALID_EVENT_ID;
+        mController = controller;
+        mViewSwitcher = viewSwitcher;
+        mGestureDetector = new GestureDetector(context, new CalendarGestureListener());
+        mNumDays = numDays;
+
+        init(context);
+    }
+
+    private void init(Context context) {
+        setFocusable(true);
+
+        // Allow focus in touch mode so that we can do keyboard shortcuts
+        // even after we've entered touch mode.
+        setFocusableInTouchMode(true);
+        setClickable(true);
+        setOnCreateContextMenuListener(this);
+
+        mFirstDayOfWeek = Utils.getFirstDayOfWeek(context);
+
+        mCurrentTime = new Time(Utils.getTimeZone(context, mTZUpdater));
+        long currentTime = System.currentTimeMillis();
+        mCurrentTime.set(currentTime);
+        //The % makes it go off at the next increment of 5 minutes.
+        postDelayed(mUpdateCurrentTime,
+                UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
+        mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
+
+        mWeek_saturdayColor = mResources.getColor(R.color.week_saturday);
+        mWeek_sundayColor = mResources.getColor(R.color.week_sunday);
+        mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color);
+        mCalendarAllDayBackground = mResources.getColor(R.color.calendar_all_day_background);
+        mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label);
+        mCalendarDateBannerBackground = mResources.getColor(R.color.calendar_date_banner_background);
+        mCalendarDateSelected = mResources.getColor(R.color.calendar_date_selected);
+        mCalendarGridAreaBackground = mResources.getColor(R.color.calendar_grid_area_background);
+        mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected);
+        mCalendarGridLineHorizontalColor = mResources.getColor(R.color.calendar_grid_line_horizontal_color);
+        mCalendarGridLineVerticalColor = mResources.getColor(R.color.calendar_grid_line_vertical_color);
+        mCalendarHourBackground = mResources.getColor(R.color.calendar_hour_background);
+        mCalendarHourLabel = mResources.getColor(R.color.calendar_hour_label);
+        mCalendarHourSelected = mResources.getColor(R.color.calendar_hour_selected);
+        mSelectionColor = mResources.getColor(R.color.selection);
+        mPressedColor = mResources.getColor(R.color.pressed);
+        mSelectedEventTextColor = mResources.getColor(R.color.calendar_event_selected_text_color);
+        mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
+        mCurrentTimeMarkerColor = mResources.getColor(R.color.current_time_marker);
+        mCurrentTimeMarkerBorderColor = mResources.getColor(R.color.current_time_marker_border);
+        mEventTextPaint.setColor(mEventTextColor);
+        mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
+        mEventTextPaint.setTextAlign(Paint.Align.LEFT);
+        mEventTextPaint.setAntiAlias(true);
+
+        int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
+        Paint p = mSelectionPaint;
+        p.setColor(gridLineColor);
+        p.setStyle(Style.STROKE);
+        p.setStrokeWidth(2.0f);
+        p.setAntiAlias(false);
+
+        p = mPaint;
+        p.setAntiAlias(true);
+
+        mPaintBorder.setColor(0xffc8c8c8);
+        mPaintBorder.setStyle(Style.STROKE);
+        mPaintBorder.setAntiAlias(true);
+        mPaintBorder.setStrokeWidth(2.0f);
+
+        // Allocate space for 2 weeks worth of weekday names so that we can
+        // easily start the week display at any week day.
+        mDayStrs = new String[14];
+
+        // Also create an array of 2-letter abbreviations.
+        mDayStrs2Letter = new String[14];
+
+        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
+            int index = i - Calendar.SUNDAY;
+            // e.g. Tue for Tuesday
+            mDayStrs[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM);
+            mDayStrs[index + 7] = mDayStrs[index];
+            // e.g. Tu for Tuesday
+            mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT);
+
+            // If we don't have 2-letter day strings, fall back to 1-letter.
+            if (mDayStrs2Letter[index].equals(mDayStrs[index])) {
+                mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORTEST);
+            }
+
+            mDayStrs2Letter[index + 7] = mDayStrs2Letter[index];
+        }
+
+        // Figure out how much space we need for the 3-letter abbrev names
+        // in the worst case.
+        p.setTextSize(NORMAL_FONT_SIZE);
+        p.setTypeface(mBold);
+        String[] dateStrs = {" 28", " 30"};
+        mDateStrWidth = computeMaxStringWidth(0, dateStrs, p);
+        mDateStrWidth += computeMaxStringWidth(0, mDayStrs, p);
+
+        p.setTextSize(HOURS_FONT_SIZE);
+        p.setTypeface(null);
+        updateIs24HourFormat();
+
+        mAmString = DateUtils.getAMPMString(Calendar.AM);
+        mPmString = DateUtils.getAMPMString(Calendar.PM);
+        String[] ampm = {mAmString, mPmString};
+        p.setTextSize(AMPM_FONT_SIZE);
+        mHoursWidth = computeMaxStringWidth(mHoursWidth, ampm, p);
+        mHoursWidth += HOURS_MARGIN;
+
+        LayoutInflater inflater;
+        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPopupView = inflater.inflate(R.layout.bubble_event, null);
+        mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+        mPopup = new PopupWindow(context);
+        mPopup.setContentView(mPopupView);
+        Resources.Theme dialogTheme = getResources().newTheme();
+        dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
+        TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
+            android.R.attr.windowBackground });
+        mPopup.setBackgroundDrawable(ta.getDrawable(0));
+        ta.recycle();
+
+        // Enable touching the popup window
+        mPopupView.setOnClickListener(this);
+
+        mBaseDate = new Time(Utils.getTimeZone(context, mTZUpdater));
+        long millis = System.currentTimeMillis();
+        mBaseDate.set(millis);
+
+        mEarliestStartHour = new int[mNumDays];
+        mHasAllDayEvent = new boolean[mNumDays];
+
+// FRAG_TODO. Take this out.
+//        mTitleTextView = (TextView) findViewById(R.id.title);
+        mTitleTextView = new TextView(mContext);
+    }
+
+    /**
+     * This is called when the popup window is pressed.
+     */
+    public void onClick(View v) {
+        if (v == mPopupView) {
+            // Pretend it was a trackball click because that will always
+            // jump to the "View event" screen.
+            switchViews(true /* trackball */);
+        }
+    }
+
+    public void updateIs24HourFormat() {
+        mIs24HourFormat = DateFormat.is24HourFormat(mContext);
+        mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
+    }
+
+    /**
+     * Returns the start of the selected time in milliseconds since the epoch.
+     *
+     * @return selected time in UTC milliseconds since the epoch.
+     */
+    long getSelectedTimeInMillis() {
+        Time time = new Time(mBaseDate);
+        time.setJulianDay(mSelectionDay);
+        time.hour = mSelectionHour;
+
+        // We ignore the "isDst" field because we want normalize() to figure
+        // out the correct DST value and not adjust the selected time based
+        // on the current setting of DST.
+        return time.normalize(true /* ignore isDst */);
+    }
+
+    Time getSelectedTime() {
+        Time time = new Time(mBaseDate);
+        time.setJulianDay(mSelectionDay);
+        time.hour = mSelectionHour;
+
+        // We ignore the "isDst" field because we want normalize() to figure
+        // out the correct DST value and not adjust the selected time based
+        // on the current setting of DST.
+        time.normalize(true /* ignore isDst */);
+        return time;
+    }
+
+    /**
+     * Returns the start of the selected time in minutes since midnight,
+     * local time.  The derived class must ensure that this is consistent
+     * with the return value from getSelectedTimeInMillis().
+     */
+    int getSelectedMinutesSinceMidnight() {
+        return mSelectionHour * MINUTES_PER_HOUR;
+    }
+
+    public void setSelectedDay(Time time) {
+        mBaseDate.set(time);
+        mSelectionHour = mBaseDate.hour;
+        mSelectedEvent = null;
+        mPrevSelectedEvent = null;
+        long millis = mBaseDate.toMillis(false /* use isDst */);
+        mSelectionDay = Time.getJulianDay(millis, mBaseDate.gmtoff);
+        mSelectedEvents.clear();
+        mComputeSelectedEvents = true;
+
+        // Force a recalculation of the first visible hour
+        mFirstHour = -1;
+        recalc();
+        mTitleTextView.setText(mDateRange);
+
+        // Force a redraw of the selection box.
+        mSelectionMode = SELECTION_SELECTED;
+        mRemeasure = true;
+        invalidate();
+    }
+
+    public Time getSelectedDay() {
+        Time time = new Time(mBaseDate);
+        time.setJulianDay(mSelectionDay);
+        time.hour = mSelectionHour;
+
+        // We ignore the "isDst" field because we want normalize() to figure
+        // out the correct DST value and not adjust the selected time based
+        // on the current setting of DST.
+        time.normalize(true /* ignore isDst */);
+        return time;
+    }
+
+    /**
+     * return a negative number if "time" is comes before the visible time
+     * range, a positive number if "time" is after the visible time range, and 0
+     * if it is in the visible time range.
+     */
+    public int compareToVisibleTimeRange(Time time) {
+
+        int savedHour = mBaseDate.hour;
+        int savedMinute = mBaseDate.minute;
+        int savedSec = mBaseDate.second;
+
+        mBaseDate.hour = 0;
+        mBaseDate.minute = 0;
+        mBaseDate.second = 0;
+
+        Log.d(TAG, "Begin " + mBaseDate.toString());
+        Log.d(TAG, "Diff  " + time.toString());
+
+        // Compare beginning of range
+        int diff = Time.compare(time, mBaseDate);
+        if (diff > 0) {
+            // Compare end of range
+            mBaseDate.monthDay += mNumDays;
+            mBaseDate.normalize(true);
+            diff = Time.compare(time, mBaseDate);
+
+            Log.d(TAG, "End   " + mBaseDate.toString());
+
+            mBaseDate.monthDay -= mNumDays;
+            mBaseDate.normalize(true);
+            if (diff < 0) {
+                // in visible time
+                diff = 0;
+            } else if (diff == 0) {
+                // Midnight of following day
+                diff = 1;
+            }
+        }
+
+        Log.d(TAG, "Diff: " + diff);
+
+        mBaseDate.hour = savedHour;
+        mBaseDate.minute = savedMinute;
+        mBaseDate.second = savedSec;
+        return diff;
+    }
+
+    private void recalc() {
+        // Set the base date to the beginning of the week if we are displaying
+        // 7 days at a time.
+        if (mNumDays == 7) {
+            int dayOfWeek = mBaseDate.weekDay;
+            int diff = dayOfWeek - mFirstDayOfWeek;
+            if (diff != 0) {
+                if (diff < 0) {
+                    diff += 7;
+                }
+                mBaseDate.monthDay -= diff;
+                mBaseDate.normalize(true /* ignore isDst */);
+            }
+        }
+
+        final long start = mBaseDate.toMillis(false /* use isDst */);
+        long end = start;
+        mFirstJulianDay = Time.getJulianDay(start, mBaseDate.gmtoff);
+        mLastJulianDay = mFirstJulianDay + mNumDays - 1;
+
+        mMonthLength = mBaseDate.getActualMaximum(Time.MONTH_DAY);
+        mFirstVisibleDate = mBaseDate.monthDay;
+        mFirstVisibleDayOfWeek = mBaseDate.weekDay;
+
+        int flags = DateUtils.FORMAT_SHOW_YEAR;
+        if (DateFormat.is24HourFormat(mContext)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        if (mNumDays > 1) {
+            mBaseDate.monthDay += mNumDays - 1;
+            end = mBaseDate.toMillis(true /* ignore isDst */);
+            mBaseDate.monthDay -= mNumDays - 1;
+            flags |= DateUtils.FORMAT_NO_MONTH_DAY;
+        } else {
+            flags |= DateUtils.FORMAT_SHOW_WEEKDAY
+                    | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
+        }
+
+        mDateRange = DateUtils.formatDateRange(mContext, start, end, flags);
+        // Do not set the title here because this is called when executing
+        // initNextView() to prepare the Day view when sliding the finger
+        // horizontally but we don't always want to change the title.  And
+        // if we change the title here and then change it back in the caller
+        // then we get an annoying flicker.
+    }
+
+    @Override
+    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+        mViewWidth = width;
+        mViewHeight = height;
+        int gridAreaWidth = width - mHoursWidth;
+        mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
+
+        Paint p = new Paint();
+        p.setTextSize(NORMAL_FONT_SIZE);
+        int bannerTextHeight = (int) Math.abs(p.ascent());
+
+        p.setTextSize(HOURS_FONT_SIZE);
+        mHoursTextHeight = (int) Math.abs(p.ascent());
+
+        p.setTextSize(EVENT_TEXT_FONT_SIZE);
+        float ascent = -p.ascent();
+        mEventTextAscent = (int) Math.ceil(ascent);
+        float totalHeight = ascent + p.descent();
+        mEventTextHeight = (int) Math.ceil(totalHeight);
+
+        if (mNumDays > 1) {
+            mBannerPlusMargin = bannerTextHeight + 14;
+        } else {
+            mBannerPlusMargin = 0;
+        }
+
+        remeasure(width, height);
+    }
+
+    /**
+     * Measures the space needed for various parts of the view after
+     * loading new events.  This can change if there are all-day events.
+     */
+    private void remeasure(int width, int height) {
+
+        // First, clear the array of earliest start times, and the array
+        // indicating presence of an all-day event.
+        for (int day = 0; day < mNumDays; day++) {
+            mEarliestStartHour[day] = 25;  // some big number
+            mHasAllDayEvent[day] = false;
+        }
+
+        // We first measure cell height, as the value isn't affected by events.
+        // After measuring the cell height, we compute the layout relation between each event
+        // before measuring cell width, as the cell width should be adjusted along with the
+        // relation.
+        //
+        // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
+        // We should mark them as "overwapped". Though they are not overwapped logically, but
+        // minimum cell height implicitly expands the cell height of A and it should look like
+        // (1:00pm - 1:15pm) after the cell height adjustment.
+
+        // TODO: Load preference and change with pinch to zoom
+        mCellHeight = DEFAULT_CELL_HEIGHT;
+
+        // Compute the space needed for the all-day events, if any.
+        // Make a pass over all the events, and keep track of the maximum
+        // number of all-day events in any one day.  Also, keep track of
+        // the earliest event in each day.
+        int maxAllDayEvents = 0;
+        final ArrayList<Event> events = mEvents;
+        final int len = events.size();
+        for (int ii = 0; ii < len; ii++) {
+            Event event = events.get(ii);
+            if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay)
+                continue;
+            if (event.allDay) {
+                int max = event.getColumn() + 1;
+                if (maxAllDayEvents < max) {
+                    maxAllDayEvents = max;
+                }
+                int daynum = event.startDay - mFirstJulianDay;
+                int durationDays = event.endDay - event.startDay + 1;
+                if (daynum < 0) {
+                    durationDays += daynum;
+                    daynum = 0;
+                }
+                if (daynum + durationDays > mNumDays) {
+                    durationDays = mNumDays - daynum;
+                }
+                for (int day = daynum; durationDays > 0; day++, durationDays--) {
+                    mHasAllDayEvent[day] = true;
+                }
+            } else {
+                int daynum = event.startDay - mFirstJulianDay;
+                int hour = event.startTime / 60;
+                if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
+                    mEarliestStartHour[daynum] = hour;
+                }
+
+                // Also check the end hour in case the event spans more than
+                // one day.
+                daynum = event.endDay - mFirstJulianDay;
+                hour = event.endTime / 60;
+                if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
+                    mEarliestStartHour[daynum] = hour;
+                }
+            }
+        }
+        mMaxAllDayEvents = maxAllDayEvents;
+
+        // Calcurates mAllDayHeight
+
+        mFirstCell = mBannerPlusMargin;
+        int allDayHeight = 0;
+        if (maxAllDayEvents > 0) {
+            // If there is at most one all-day event per day, then use less
+            // space (but more than the space for a single event).
+            if (maxAllDayEvents == 1) {
+                allDayHeight = SINGLE_ALLDAY_HEIGHT;
+            } else {
+                // Allow the all-day area to grow in height depending on the
+                // number of all-day events we need to show, up to a limit.
+                allDayHeight = maxAllDayEvents * MAX_ALLDAY_EVENT_HEIGHT;
+                if (allDayHeight > MAX_ALLDAY_HEIGHT) {
+                    allDayHeight = MAX_ALLDAY_HEIGHT;
+                }
+            }
+            mFirstCell = mBannerPlusMargin + allDayHeight + ALLDAY_TOP_MARGIN;
+        } else {
+            mSelectionAllDay = false;
+        }
+        mAllDayHeight = allDayHeight;
+
+        mGridAreaHeight = height - mFirstCell;
+        mNumHours = mGridAreaHeight / mCellHeight;
+        mEventGeometry.setHourHeight(mCellHeight);
+
+        final float minimumDurationMillis =
+                MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f);
+        Event.computePositions(events, minimumDurationMillis);
+
+        // Create an off-screen bitmap that we can draw into.
+        mBitmapHeight = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
+        if ((mBitmap == null || mBitmap.getHeight() < mBitmapHeight) && width > 0 &&
+                mBitmapHeight > 0) {
+            if (mBitmap != null) {
+                mBitmap.recycle();
+            }
+            mBitmap = Bitmap.createBitmap(width, mBitmapHeight, Bitmap.Config.RGB_565);
+            mCanvas = new Canvas(mBitmap);
+        }
+        mMaxViewStartY = mBitmapHeight - mGridAreaHeight;
+
+        if (mFirstHour == -1) {
+            initFirstHour();
+            mFirstHourOffset = 0;
+        }
+
+        // When we change the base date, the number of all-day events may
+        // change and that changes the cell height.  When we switch dates,
+        // we use the mFirstHourOffset from the previous view, but that may
+        // be too large for the new view if the cell height is smaller.
+        if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
+            mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
+        }
+        mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
+
+        final int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
+        //When we get new events we don't want to dismiss the popup unless the event changes
+        if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent.id) {
+            mPopup.dismiss();
+        }
+        mPopup.setWidth(eventAreaWidth - 20);
+        mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
+    }
+
+    /**
+     * Initialize the state for another view.  The given view is one that has
+     * its own bitmap and will use an animation to replace the current view.
+     * The current view and new view are either both Week views or both Day
+     * views.  They differ in their base date.
+     *
+     * @param view the view to initialize.
+     */
+    private void initView(DayView view) {
+        view.mSelectionHour = mSelectionHour;
+        view.mSelectedEvents.clear();
+        view.mComputeSelectedEvents = true;
+        view.mFirstHour = mFirstHour;
+        view.mFirstHourOffset = mFirstHourOffset;
+        view.remeasure(getWidth(), getHeight());
+
+        view.mSelectedEvent = null;
+        view.mPrevSelectedEvent = null;
+        view.mFirstDayOfWeek = mFirstDayOfWeek;
+        if (view.mEvents.size() > 0) {
+            view.mSelectionAllDay = mSelectionAllDay;
+        } else {
+            view.mSelectionAllDay = false;
+        }
+
+        // Redraw the screen so that the selection box will be redrawn.  We may
+        // have scrolled to a different part of the day in some other view
+        // so the selection box in this view may no longer be visible.
+        view.recalc();
+    }
+
+    /**
+     * Switch to another view based on what was selected (an event or a free
+     * slot) and how it was selected (by touch or by trackball).
+     *
+     * @param trackBallSelection true if the selection was made using the
+     * trackball.
+     */
+    private void switchViews(boolean trackBallSelection) {
+        Event selectedEvent = mSelectedEvent;
+
+        mPopup.dismiss();
+        mLastPopupEventID = INVALID_EVENT_ID;
+        if (mNumDays > 1) {
+            // This is the Week view.
+            // With touch, we always switch to Day/Agenda View
+            // With track ball, if we selected a free slot, then create an event.
+            // If we selected a specific event, switch to EventInfo view.
+            if (trackBallSelection) {
+                if (selectedEvent == null) {
+                    // Switch to the EditEvent view
+                    long startMillis = getSelectedTimeInMillis();
+                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
+                    mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
+                            startMillis, endMillis, 0, 0);
+                } else {
+                    // Switch to the EventInfo view
+                    mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
+                            selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
+                }
+            } else {
+                // This was a touch selection.  If the touch selected a single
+                // unambiguous event, then view that event.  Otherwise go to
+                // Day/Agenda view.
+                if (mSelectedEvents.size() == 1) {
+                    mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
+                            selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
+                }
+            }
+        } else {
+            // This is the Day view.
+            // If we selected a free slot, then create an event.
+            // If we selected an event, then go to the EventInfo view.
+            if (selectedEvent == null) {
+                // Switch to the EditEvent view
+                long startMillis = getSelectedTimeInMillis();
+                long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
+
+                mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1, startMillis,
+                        endMillis, 0, 0);
+            } else {
+                mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
+                        selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
+            }
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        mScrolling = false;
+        long duration = event.getEventTime() - event.getDownTime();
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (mSelectionMode == SELECTION_HIDDEN) {
+                    // Don't do anything unless the selection is visible.
+                    break;
+                }
+
+                if (mSelectionMode == SELECTION_PRESSED) {
+                    // This was the first press when there was nothing selected.
+                    // Change the selection from the "pressed" state to the
+                    // the "selected" state.  We treat short-press and
+                    // long-press the same here because nothing was selected.
+                    mSelectionMode = SELECTION_SELECTED;
+                    invalidate();
+                    break;
+                }
+
+                // Check the duration to determine if this was a short press
+                if (duration < ViewConfiguration.getLongPressTimeout()) {
+                    switchViews(true /* trackball */);
+                } else {
+                    mSelectionMode = SELECTION_LONGPRESS;
+                    invalidate();
+                    performLongClick();
+                }
+                break;
+//            case KeyEvent.KEYCODE_BACK:
+//                if (event.isTracking() && !event.isCanceled()) {
+//                    mPopup.dismiss();
+//                    mContext.finish();
+//                    return true;
+//                }
+//                break;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mSelectionMode == SELECTION_HIDDEN) {
+            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
+                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                // Display the selection box but don't move or select it
+                // on this key press.
+                mSelectionMode = SELECTION_SELECTED;
+                invalidate();
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+                // Display the selection box but don't select it
+                // on this key press.
+                mSelectionMode = SELECTION_PRESSED;
+                invalidate();
+                return true;
+            }
+        }
+
+        mSelectionMode = SELECTION_SELECTED;
+        mScrolling = false;
+        boolean redraw;
+        int selectionDay = mSelectionDay;
+
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DEL:
+            // Delete the selected event, if any
+            Event selectedEvent = mSelectedEvent;
+            if (selectedEvent == null) {
+                return false;
+            }
+            mPopup.dismiss();
+            mLastPopupEventID = INVALID_EVENT_ID;
+
+            long begin = selectedEvent.startMillis;
+            long end = selectedEvent.endMillis;
+            long id = selectedEvent.id;
+            mDeleteEventHelper.delete(begin, end, id, -1);
+            return true;
+        case KeyEvent.KEYCODE_ENTER:
+            switchViews(true /* trackball or keyboard */);
+            return true;
+        case KeyEvent.KEYCODE_BACK:
+            if (event.getRepeatCount() == 0) {
+                event.startTracking();
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextLeft;
+            }
+            if (mSelectedEvent == null) {
+                mLastPopupEventID = INVALID_EVENT_ID;
+                selectionDay -= 1;
+            }
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextRight;
+            }
+            if (mSelectedEvent == null) {
+                mLastPopupEventID = INVALID_EVENT_ID;
+                selectionDay += 1;
+            }
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_UP:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextUp;
+            }
+            if (mSelectedEvent == null) {
+                mLastPopupEventID = INVALID_EVENT_ID;
+                if (!mSelectionAllDay) {
+                    mSelectionHour -= 1;
+                    adjustHourSelection();
+                    mSelectedEvents.clear();
+                    mComputeSelectedEvents = true;
+                }
+            }
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextDown;
+            }
+            if (mSelectedEvent == null) {
+                mLastPopupEventID = INVALID_EVENT_ID;
+                if (mSelectionAllDay) {
+                    mSelectionAllDay = false;
+                } else {
+                    mSelectionHour++;
+                    adjustHourSelection();
+                    mSelectedEvents.clear();
+                    mComputeSelectedEvents = true;
+                }
+            }
+            redraw = true;
+            break;
+
+        default:
+            return super.onKeyDown(keyCode, event);
+        }
+
+        if ((selectionDay < mFirstJulianDay) || (selectionDay > mLastJulianDay)) {
+            boolean forward;
+            DayView view = (DayView) mViewSwitcher.getNextView();
+            Time date = view.mBaseDate;
+            date.set(mBaseDate);
+            if (selectionDay < mFirstJulianDay) {
+                date.monthDay -= mNumDays;
+                forward = false;
+            } else {
+                date.monthDay += mNumDays;
+                forward = true;
+            }
+            date.normalize(true /* ignore isDst */);
+            view.mSelectionDay = selectionDay;
+
+            initView(view);
+
+            Time end = new Time(date);
+            end.monthDay += mNumDays - 1;
+            Log.d(TAG, "onKeyDown");
+            mController.sendEvent(this, EventType.GO_TO, date, end, -1, ViewType.CURRENT);
+
+            mTitleTextView.setText(view.mDateRange);
+            return true;
+        }
+        mSelectionDay = selectionDay;
+        mSelectedEvents.clear();
+        mComputeSelectedEvents = true;
+
+        if (redraw) {
+            invalidate();
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private View switchViews(boolean forward, float xOffSet, float width) {
+        float progress = Math.abs(xOffSet) / width;
+        if (progress > 1.0f) {
+            progress = 1.0f;
+        }
+
+        float inFromXValue, inToXValue;
+        float outFromXValue, outToXValue;
+        if (forward) {
+            inFromXValue = 1.0f - progress;
+            inToXValue = 0.0f;
+            outFromXValue = -progress;
+            outToXValue = -1.0f;
+        } else {
+            inFromXValue = progress - 1.0f;
+            inToXValue = 0.0f;
+            outFromXValue = progress;
+            outToXValue = 1.0f;
+        }
+
+        // We have to allocate these animation objects each time we switch views
+        // because that is the only way to set the animation parameters.
+        TranslateAnimation inAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, inFromXValue,
+                Animation.RELATIVE_TO_SELF, inToXValue,
+                Animation.ABSOLUTE, 0.0f,
+                Animation.ABSOLUTE, 0.0f);
+
+        TranslateAnimation outAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, outFromXValue,
+                Animation.RELATIVE_TO_SELF, outToXValue,
+                Animation.ABSOLUTE, 0.0f,
+                Animation.ABSOLUTE, 0.0f);
+
+        // Reduce the animation duration based on how far we have already swiped.
+        long duration = (long) (ANIMATION_DURATION * (1.0f - progress));
+        inAnimation.setDuration(duration);
+        outAnimation.setDuration(duration);
+        mViewSwitcher.setInAnimation(inAnimation);
+        mViewSwitcher.setOutAnimation(outAnimation);
+
+        DayView view = (DayView) mViewSwitcher.getCurrentView();
+        view.cleanup();
+        mViewSwitcher.showNext();
+        view = (DayView) mViewSwitcher.getCurrentView();
+        view.requestFocus();
+        view.reloadEvents();
+        return view;
+    }
+
+    // This is called after scrolling stops to move the selected hour
+    // to the visible part of the screen.
+    private void resetSelectedHour() {
+        if (mSelectionHour < mFirstHour + 1) {
+            mSelectionHour = mFirstHour + 1;
+            mSelectedEvent = null;
+            mSelectedEvents.clear();
+            mComputeSelectedEvents = true;
+        } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
+            mSelectionHour = mFirstHour + mNumHours - 3;
+            mSelectedEvent = null;
+            mSelectedEvents.clear();
+            mComputeSelectedEvents = true;
+        }
+    }
+
+    private void initFirstHour() {
+        mFirstHour = mSelectionHour - mNumHours / 2;
+        if (mFirstHour < 0) {
+            mFirstHour = 0;
+        } else if (mFirstHour + mNumHours > 24) {
+            mFirstHour = 24 - mNumHours;
+        }
+    }
+
+    /**
+     * Recomputes the first full hour that is visible on screen after the
+     * screen is scrolled.
+     */
+    private void computeFirstHour() {
+        // Compute the first full hour that is visible on screen
+        mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP);
+        mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY;
+    }
+
+    private void adjustHourSelection() {
+        if (mSelectionHour < 0) {
+            mSelectionHour = 0;
+            if (mMaxAllDayEvents > 0) {
+                mPrevSelectedEvent = null;
+                mSelectionAllDay = true;
+            }
+        }
+
+        if (mSelectionHour > 23) {
+            mSelectionHour = 23;
+        }
+
+        // If the selected hour is at least 2 time slots from the top and
+        // bottom of the screen, then don't scroll the view.
+        if (mSelectionHour < mFirstHour + 1) {
+            // If there are all-days events for the selected day but there
+            // are no more normal events earlier in the day, then jump to
+            // the all-day event area.
+            // Exception 1: allow the user to scroll to 8am with the trackball
+            // before jumping to the all-day event area.
+            // Exception 2: if 12am is on screen, then allow the user to select
+            // 12am before going up to the all-day event area.
+            int daynum = mSelectionDay - mFirstJulianDay;
+            if (mMaxAllDayEvents > 0 && mEarliestStartHour[daynum] > mSelectionHour
+                    && mFirstHour > 0 && mFirstHour < 8) {
+                mPrevSelectedEvent = null;
+                mSelectionAllDay = true;
+                mSelectionHour = mFirstHour + 1;
+                return;
+            }
+
+            if (mFirstHour > 0) {
+                mFirstHour -= 1;
+                mViewStartY -= (mCellHeight + HOUR_GAP);
+                if (mViewStartY < 0) {
+                    mViewStartY = 0;
+                }
+                return;
+            }
+        }
+
+        if (mSelectionHour > mFirstHour + mNumHours - 3) {
+            if (mFirstHour < 24 - mNumHours) {
+                mFirstHour += 1;
+                mViewStartY += (mCellHeight + HOUR_GAP);
+                if (mViewStartY > mBitmapHeight - mGridAreaHeight) {
+                    mViewStartY = mBitmapHeight - mGridAreaHeight;
+                }
+                return;
+            } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
+                mViewStartY = mBitmapHeight - mGridAreaHeight;
+            }
+        }
+    }
+
+    void clearCachedEvents() {
+        mLastReloadMillis = 0;
+    }
+
+    private Runnable mCancelCallback = new Runnable() {
+        public void run() {
+            clearCachedEvents();
+        }
+    };
+
+    /* package */ void reloadEvents() {
+        // Protect against this being called before this view has been
+        // initialized.
+//        if (mContext == null) {
+//            return;
+//        }
+
+        // Make sure our time zones are up to date
+        mTZUpdater.run();
+
+        mSelectedEvent = null;
+        mPrevSelectedEvent = null;
+        mSelectedEvents.clear();
+
+        // The start date is the beginning of the week at 12am
+        Time weekStart = new Time(Utils.getTimeZone(mContext, mTZUpdater));
+        weekStart.set(mBaseDate);
+        weekStart.hour = 0;
+        weekStart.minute = 0;
+        weekStart.second = 0;
+        long millis = weekStart.normalize(true /* ignore isDst */);
+
+        // Avoid reloading events unnecessarily.
+        if (millis == mLastReloadMillis) {
+            return;
+        }
+        mLastReloadMillis = millis;
+
+        // load events in the background
+//        mContext.startProgressSpinner();
+        final ArrayList<Event> events = new ArrayList<Event>();
+        mEventLoader.loadEventsInBackground(mNumDays, events, millis, new Runnable() {
+            public void run() {
+                mEvents = events;
+                mRemeasure = true;
+                mComputeSelectedEvents = true;
+                recalc();
+//                mContext.stopProgressSpinner();
+                invalidate();
+            }
+        }, mCancelCallback);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mRemeasure) {
+            remeasure(getWidth(), getHeight());
+            mRemeasure = false;
+        }
+
+        if (mCanvas != null) {
+            doDraw(mCanvas);
+        }
+
+        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
+            canvas.save();
+            if (mViewStartX > 0) {
+                canvas.translate(mViewWidth - mViewStartX, 0);
+            } else {
+                canvas.translate(-(mViewWidth + mViewStartX), 0);
+            }
+            DayView nextView = (DayView) mViewSwitcher.getNextView();
+
+            // Prevent infinite recursive calls to onDraw().
+            nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE;
+
+            nextView.onDraw(canvas);
+            canvas.restore();
+            canvas.save();
+            canvas.translate(-mViewStartX, 0);
+        }
+
+        if (mBitmap != null) {
+            drawCalendarView(canvas);
+        }
+
+        // Draw the fixed areas (that don't scroll) directly to the canvas.
+        drawAfterScroll(canvas);
+        mComputeSelectedEvents = false;
+
+        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
+            canvas.restore();
+        }
+    }
+
+    private void drawCalendarView(Canvas canvas) {
+
+        // Copy the scrollable region from the big bitmap to the canvas.
+        Rect src = mSrcRect;
+        Rect dest = mDestRect;
+
+        src.top = mViewStartY;
+        src.bottom = mViewStartY + mGridAreaHeight;
+        src.left = 0;
+        src.right = mViewWidth;
+
+        dest.top = mFirstCell;
+        dest.bottom = mViewHeight;
+        dest.left = 0;
+        dest.right = mViewWidth;
+
+        canvas.save();
+        canvas.clipRect(dest);
+        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        canvas.drawBitmap(mBitmap, src, dest, null);
+        canvas.restore();
+    }
+
+    private void drawAfterScroll(Canvas canvas) {
+        Paint p = mPaint;
+        Rect r = mRect;
+
+        if (mMaxAllDayEvents != 0) {
+            drawAllDayEvents(mFirstJulianDay, mNumDays, r, canvas, p);
+            drawUpperLeftCorner(r, canvas, p);
+        }
+
+        if (mNumDays > 1) {
+            drawDayHeaderLoop(r, canvas, p);
+        }
+
+        // Draw the AM and PM indicators if we're in 12 hour mode
+        if (!mIs24HourFormat) {
+            drawAmPm(canvas, p);
+        }
+
+        // Update the popup window showing the event details, but only if
+        // we are not scrolling and we have focus.
+        if (!mScrolling && isFocused()) {
+            updateEventDetails();
+        }
+    }
+
+    // This isn't really the upper-left corner.  It's the square area just
+    // below the upper-left corner, above the hours and to the left of the
+    // all-day area.
+    private void drawUpperLeftCorner(Rect r, Canvas canvas, Paint p) {
+        p.setColor(mCalendarHourBackground);
+        r.top = mBannerPlusMargin;
+        r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
+        r.left = 0;
+        r.right = mHoursWidth;
+        canvas.drawRect(r, p);
+    }
+
+    private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) {
+        // Draw the horizontal day background banner
+        p.setColor(mCalendarDateBannerBackground);
+        r.top = 0;
+        r.bottom = mBannerPlusMargin;
+        r.left = 0;
+        r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
+        canvas.drawRect(r, p);
+
+        // Fill the extra space on the right side with the default background
+        r.left = r.right;
+        r.right = mViewWidth;
+        p.setColor(mCalendarGridAreaBackground);
+        canvas.drawRect(r, p);
+
+        // Draw a highlight on the selected day (if any), but only if we are
+        // displaying more than one day.
+        if (mSelectionMode != SELECTION_HIDDEN) {
+            if (mNumDays > 1) {
+                p.setColor(mCalendarDateSelected);
+                r.top = 0;
+                r.bottom = mBannerPlusMargin;
+                int daynum = mSelectionDay - mFirstJulianDay;
+                r.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
+                r.right = r.left + mCellWidth;
+                canvas.drawRect(r, p);
+            }
+        }
+
+        p.setTextSize(NORMAL_FONT_SIZE);
+        p.setTextAlign(Paint.Align.CENTER);
+        int x = mHoursWidth;
+        int deltaX = mCellWidth + DAY_GAP;
+        int cell = mFirstJulianDay;
+
+        String[] dayNames;
+        if (mDateStrWidth < mCellWidth) {
+            dayNames = mDayStrs;
+        } else {
+            dayNames = mDayStrs2Letter;
+        }
+
+        p.setTypeface(mBold);
+        p.setAntiAlias(true);
+        for (int day = 0; day < mNumDays; day++, cell++) {
+            int dayOfWeek = day + mFirstVisibleDayOfWeek;
+            if (dayOfWeek >= 14) {
+                dayOfWeek -= 14;
+            }
+
+            if (Utils.isSaturday(dayOfWeek, mFirstDayOfWeek)) {
+                p.setColor(mWeek_saturdayColor);
+            } else if (Utils.isSunday(dayOfWeek, mFirstDayOfWeek)) {
+                p.setColor(mWeek_sundayColor);
+            } else {
+                p.setColor(mCalendarDateBannerTextColor);
+            }
+
+            drawDayHeader(dayNames[dayOfWeek], day, cell, x, canvas, p);
+            x += deltaX;
+        }
+    }
+
+    private void drawAmPm(Canvas canvas, Paint p) {
+        p.setColor(mCalendarAmPmLabel);
+        p.setTextSize(AMPM_FONT_SIZE);
+        p.setTypeface(mBold);
+        p.setAntiAlias(true);
+        mPaint.setTextAlign(Paint.Align.RIGHT);
+        String text = mAmString;
+        if (mFirstHour >= 12) {
+            text = mPmString;
+        }
+        int y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP;
+        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
+        canvas.drawText(text, right, y, p);
+
+        if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
+            // Also draw the "PM"
+            text = mPmString;
+            y = mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP)
+                    + 2 * mHoursTextHeight + HOUR_GAP;
+            canvas.drawText(text, right, y, p);
+        }
+    }
+
+    private void drawCurrentTimeMarker(int top, Canvas canvas, Paint p) {
+        top -= CURRENT_TIME_MARKER_HEIGHT / 2;
+        p.setColor(mCurrentTimeMarkerColor);
+        Paint.Style oldStyle = p.getStyle();
+        p.setStyle(Paint.Style.STROKE);
+        p.setStrokeWidth(2.0f);
+        Path mCurrentTimeMarker = mPath;
+        mCurrentTimeMarker.reset();
+        mCurrentTimeMarker.moveTo(0, top);
+        mCurrentTimeMarker.lineTo(0, CURRENT_TIME_MARKER_HEIGHT + top);
+        mCurrentTimeMarker.lineTo(CURRENT_TIME_MARKER_INNER_WIDTH, CURRENT_TIME_MARKER_HEIGHT + top);
+        mCurrentTimeMarker.lineTo(CURRENT_TIME_MARKER_WIDTH, CURRENT_TIME_MARKER_HEIGHT / 2 + top);
+        mCurrentTimeMarker.lineTo(CURRENT_TIME_MARKER_INNER_WIDTH, top);
+        mCurrentTimeMarker.lineTo(0, top);
+        canvas.drawPath(mCurrentTimeMarker, p);
+        p.setStyle(oldStyle);
+    }
+
+    private void drawCurrentTimeLine(Rect r, int left, int top, Canvas canvas, Paint p) {
+        //Do a white outline so it'll show up on a red event
+        p.setColor(mCurrentTimeMarkerBorderColor);
+        r.top = top - CURRENT_TIME_LINE_HEIGHT / 2 - CURRENT_TIME_LINE_BORDER_WIDTH;
+        r.bottom = top + CURRENT_TIME_LINE_HEIGHT / 2 + CURRENT_TIME_LINE_BORDER_WIDTH;
+        r.left = left + CURRENT_TIME_LINE_SIDE_BUFFER;
+        r.right = r.left + mCellWidth - 2 * CURRENT_TIME_LINE_SIDE_BUFFER;
+        canvas.drawRect(r, p);
+        //Then draw the red line
+        p.setColor(mCurrentTimeMarkerColor);
+        r.top = top - CURRENT_TIME_LINE_HEIGHT / 2;
+        r.bottom = top + CURRENT_TIME_LINE_HEIGHT / 2;
+        canvas.drawRect(r, p);
+    }
+
+    private void doDraw(Canvas canvas) {
+        Paint p = mPaint;
+        Rect r = mRect;
+        int lineY = mCurrentTime.hour*(mCellHeight + HOUR_GAP)
+            + ((mCurrentTime.minute * mCellHeight) / 60)
+            + 1;
+
+        drawGridBackground(r, canvas, p);
+        drawHours(r, canvas, p);
+
+        // Draw each day
+        int x = mHoursWidth;
+        int deltaX = mCellWidth + DAY_GAP;
+        int cell = mFirstJulianDay;
+        for (int day = 0; day < mNumDays; day++, cell++) {
+            drawEvents(cell, x, HOUR_GAP, canvas, p);
+            //If this is today
+            if(cell == mTodayJulianDay) {
+                //And the current time shows up somewhere on the screen
+                if(lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
+                    //draw both the marker and the line
+                    drawCurrentTimeMarker(lineY, canvas, p);
+                    drawCurrentTimeLine(r, x, lineY, canvas, p);
+                }
+            }
+            x += deltaX;
+        }
+    }
+
+    private void drawHours(Rect r, Canvas canvas, Paint p) {
+        // Draw the background for the hour labels
+        p.setColor(mCalendarHourBackground);
+        r.top = 0;
+        r.bottom = 24 * (mCellHeight + HOUR_GAP) + HOUR_GAP;
+        r.left = 0;
+        r.right = mHoursWidth;
+        canvas.drawRect(r, p);
+
+        // Fill the bottom left corner with the default grid background
+        r.top = r.bottom;
+        r.bottom = mBitmapHeight;
+        p.setColor(mCalendarGridAreaBackground);
+        canvas.drawRect(r, p);
+
+        // Draw a highlight on the selected hour (if needed)
+        if (mSelectionMode != SELECTION_HIDDEN && !mSelectionAllDay) {
+            p.setColor(mCalendarHourSelected);
+            r.top = mSelectionHour * (mCellHeight + HOUR_GAP);
+            r.bottom = r.top + mCellHeight + 2 * HOUR_GAP;
+            r.left = 0;
+            r.right = mHoursWidth;
+            canvas.drawRect(r, p);
+
+            // Also draw the highlight on the grid
+            p.setColor(mCalendarGridAreaSelected);
+            int daynum = mSelectionDay - mFirstJulianDay;
+            r.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
+            r.right = r.left + mCellWidth;
+            canvas.drawRect(r, p);
+
+            // Draw a border around the highlighted grid hour.
+            Path path = mPath;
+            r.top += HOUR_GAP;
+            r.bottom -= HOUR_GAP;
+            path.reset();
+            path.addRect(r.left, r.top, r.right, r.bottom, Direction.CW);
+            canvas.drawPath(path, mSelectionPaint);
+            saveSelectionPosition(r.left, r.top, r.right, r.bottom);
+        }
+
+        p.setColor(mCalendarHourLabel);
+        p.setTextSize(HOURS_FONT_SIZE);
+        p.setTypeface(mBold);
+        p.setTextAlign(Paint.Align.RIGHT);
+        p.setAntiAlias(true);
+
+        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
+        int y = HOUR_GAP + mHoursTextHeight;
+
+        for (int i = 0; i < 24; i++) {
+            String time = mHourStrs[i];
+            canvas.drawText(time, right, y, p);
+            y += mCellHeight + HOUR_GAP;
+        }
+    }
+
+    private void drawDayHeader(String dateStr, int day, int cell, int x, Canvas canvas, Paint p) {
+        float xCenter = x + mCellWidth / 2.0f;
+
+        int dateNum = mFirstVisibleDate + day;
+        if (dateNum > mMonthLength) {
+            dateNum -= mMonthLength;
+        }
+
+        String dateNumStr;
+        // Add a leading zero if the date is a single digit
+        if (dateNum < 10) {
+            dateNumStr = "0" + dateNum;
+        } else {
+            dateNumStr = String.valueOf(dateNum);
+        }
+
+        DayHeader header = dayHeaders[day];
+        if (header == null || header.cell != cell) {
+            // The day header string is regenerated on every draw during drag and fling animation.
+            // Caching day header since formatting the string takes surprising long time.
+
+            dayHeaders[day] = new DayHeader();
+            dayHeaders[day].cell = cell;
+            dayHeaders[day].dateString = getResources().getString(
+                    R.string.weekday_day, dateStr, dateNumStr);
+        }
+        dateStr = dayHeaders[day].dateString;
+
+        float y = mBannerPlusMargin - 7;
+        canvas.drawText(dateStr, xCenter, y, p);
+    }
+
+    private void drawGridBackground(Rect r, Canvas canvas, Paint p) {
+        Paint.Style savedStyle = p.getStyle();
+
+        // Clear the background
+        p.setColor(mCalendarGridAreaBackground);
+        r.top = 0;
+        r.bottom = mBitmapHeight;
+        r.left = 0;
+        r.right = mViewWidth;
+        canvas.drawRect(r, p);
+
+        // Draw the horizontal grid lines
+        p.setColor(mCalendarGridLineHorizontalColor);
+        p.setStyle(Style.STROKE);
+        p.setStrokeWidth(0);
+        p.setAntiAlias(false);
+        float startX = mHoursWidth;
+        float stopX = mHoursWidth + (mCellWidth + DAY_GAP) * mNumDays;
+        float y = 0;
+        float deltaY = mCellHeight + HOUR_GAP;
+        for (int hour = 0; hour <= 24; hour++) {
+            canvas.drawLine(startX, y, stopX, y, p);
+            y += deltaY;
+        }
+
+        // Draw the vertical grid lines
+        p.setColor(mCalendarGridLineVerticalColor);
+        float startY = 0;
+        float stopY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
+        float deltaX = mCellWidth + DAY_GAP;
+        float x = mHoursWidth + mCellWidth;
+        for (int day = 0; day < mNumDays; day++) {
+            canvas.drawLine(x, startY, x, stopY, p);
+            x += deltaX;
+        }
+
+        // Restore the saved style.
+        p.setStyle(savedStyle);
+        p.setAntiAlias(true);
+    }
+
+    Event getSelectedEvent() {
+        if (mSelectedEvent == null) {
+            // There is no event at the selected hour, so create a new event.
+            return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
+                    getSelectedMinutesSinceMidnight());
+        }
+        return mSelectedEvent;
+    }
+
+    boolean isEventSelected() {
+        return (mSelectedEvent != null);
+    }
+
+    Event getNewEvent() {
+        return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
+                getSelectedMinutesSinceMidnight());
+    }
+
+    static Event getNewEvent(int julianDay, long utcMillis,
+            int minutesSinceMidnight) {
+        Event event = Event.newInstance();
+        event.startDay = julianDay;
+        event.endDay = julianDay;
+        event.startMillis = utcMillis;
+        event.endMillis = event.startMillis + MILLIS_PER_HOUR;
+        event.startTime = minutesSinceMidnight;
+        event.endTime = event.startTime + MINUTES_PER_HOUR;
+        return event;
+    }
+
+    private int computeMaxStringWidth(int currentMax, String[] strings, Paint p) {
+        float maxWidthF = 0.0f;
+
+        int len = strings.length;
+        for (int i = 0; i < len; i++) {
+            float width = p.measureText(strings[i]);
+            maxWidthF = Math.max(width, maxWidthF);
+        }
+        int maxWidth = (int) (maxWidthF + 0.5);
+        if (maxWidth < currentMax) {
+            maxWidth = currentMax;
+        }
+        return maxWidth;
+    }
+
+    private void saveSelectionPosition(float left, float top, float right, float bottom) {
+        mPrevBox.left = (int) left;
+        mPrevBox.right = (int) right;
+        mPrevBox.top = (int) top;
+        mPrevBox.bottom = (int) bottom;
+    }
+
+    private Rect getCurrentSelectionPosition() {
+        Rect box = new Rect();
+        box.top = mSelectionHour * (mCellHeight + HOUR_GAP);
+        box.bottom = box.top + mCellHeight + HOUR_GAP;
+        int daynum = mSelectionDay - mFirstJulianDay;
+        box.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
+        box.right = box.left + mCellWidth + DAY_GAP;
+        return box;
+    }
+
+    private void drawAllDayEvents(int firstDay, int numDays,
+            Rect r, Canvas canvas, Paint p) {
+        p.setTextSize(NORMAL_FONT_SIZE);
+        p.setTextAlign(Paint.Align.LEFT);
+        Paint eventTextPaint = mEventTextPaint;
+
+        // Draw the background for the all-day events area
+        r.top = mBannerPlusMargin;
+        r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
+        r.left = mHoursWidth;
+        r.right = r.left + mNumDays * (mCellWidth + DAY_GAP);
+        p.setColor(mCalendarAllDayBackground);
+        canvas.drawRect(r, p);
+
+        // Fill the extra space on the right side with the default background
+        r.left = r.right;
+        r.right = mViewWidth;
+        p.setColor(mCalendarGridAreaBackground);
+        canvas.drawRect(r, p);
+
+        // Draw the vertical grid lines
+        p.setColor(mCalendarGridLineVerticalColor);
+        p.setStyle(Style.STROKE);
+        p.setStrokeWidth(0);
+        p.setAntiAlias(false);
+        float startY = r.top;
+        float stopY = r.bottom;
+        float deltaX = mCellWidth + DAY_GAP;
+        float x = mHoursWidth + mCellWidth;
+        for (int day = 0; day <= mNumDays; day++) {
+            canvas.drawLine(x, startY, x, stopY, p);
+            x += deltaX;
+        }
+        p.setAntiAlias(true);
+        p.setStyle(Style.FILL);
+
+        int y = mBannerPlusMargin + ALLDAY_TOP_MARGIN;
+        float left = mHoursWidth;
+        int lastDay = firstDay + numDays - 1;
+        ArrayList<Event> events = mEvents;
+        int numEvents = events.size();
+        float drawHeight = mAllDayHeight;
+        float numRectangles = mMaxAllDayEvents;
+        for (int i = 0; i < numEvents; i++) {
+            Event event = events.get(i);
+            if (!event.allDay)
+                continue;
+            int startDay = event.startDay;
+            int endDay = event.endDay;
+            if (startDay > lastDay || endDay < firstDay)
+                continue;
+            if (startDay < firstDay)
+                startDay = firstDay;
+            if (endDay > lastDay)
+                endDay = lastDay;
+            int startIndex = startDay - firstDay;
+            int endIndex = endDay - firstDay;
+            float height = drawHeight / numRectangles;
+
+            // Prevent a single event from getting too big
+            if (height > MAX_ALLDAY_EVENT_HEIGHT) {
+                height = MAX_ALLDAY_EVENT_HEIGHT;
+            }
+
+            // Leave a one-pixel space between the vertical day lines and the
+            // event rectangle.
+            event.left = left + startIndex * (mCellWidth + DAY_GAP) + 2;
+            event.right = left + endIndex * (mCellWidth + DAY_GAP) + mCellWidth - 1;
+            event.top = y + height * event.getColumn();
+
+            // Multiply the height by 0.9 to leave a little gap between events
+            event.bottom = event.top + height * 0.9f;
+
+            RectF rf = drawAllDayEventRect(event, canvas, p, eventTextPaint);
+            drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
+
+            // Check if this all-day event intersects the selected day
+            if (mSelectionAllDay && mComputeSelectedEvents) {
+                if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
+                    mSelectedEvents.add(event);
+                }
+            }
+        }
+
+        if (mSelectionAllDay) {
+            // Compute the neighbors for the list of all-day events that
+            // intersect the selected day.
+            computeAllDayNeighbors();
+            if (mSelectedEvent != null) {
+                Event event = mSelectedEvent;
+                RectF rf = drawAllDayEventRect(event, canvas, p, eventTextPaint);
+                drawEventText(event, rf, canvas, eventTextPaint, ALL_DAY_TEXT_TOP_MARGIN);
+            }
+
+            // Draw the highlight on the selected all-day area
+            float top = mBannerPlusMargin + 1;
+            float bottom = top + mAllDayHeight + ALLDAY_TOP_MARGIN - 1;
+            int daynum = mSelectionDay - mFirstJulianDay;
+            left = mHoursWidth + daynum * (mCellWidth + DAY_GAP) + 1;
+            float right = left + mCellWidth + DAY_GAP - 1;
+            if (mNumDays == 1) {
+                // The Day view doesn't have a vertical line on the right.
+                right -= 1;
+            }
+            Path path = mPath;
+            path.reset();
+            path.addRect(left, top, right, bottom, Direction.CW);
+            canvas.drawPath(path, mSelectionPaint);
+
+            // Set the selection position to zero so that when we move down
+            // to the normal event area, we will highlight the topmost event.
+            saveSelectionPosition(0f, 0f, 0f, 0f);
+        }
+    }
+
+    private void computeAllDayNeighbors() {
+        int len = mSelectedEvents.size();
+        if (len == 0 || mSelectedEvent != null) {
+            return;
+        }
+
+        // First, clear all the links
+        for (int ii = 0; ii < len; ii++) {
+            Event ev = mSelectedEvents.get(ii);
+            ev.nextUp = null;
+            ev.nextDown = null;
+            ev.nextLeft = null;
+            ev.nextRight = null;
+        }
+
+        // For each event in the selected event list "mSelectedEvents", find
+        // its neighbors in the up and down directions.  This could be done
+        // more efficiently by sorting on the Event.getColumn() field, but
+        // the list is expected to be very small.
+
+        // Find the event in the same row as the previously selected all-day
+        // event, if any.
+        int startPosition = -1;
+        if (mPrevSelectedEvent != null && mPrevSelectedEvent.allDay) {
+            startPosition = mPrevSelectedEvent.getColumn();
+        }
+        int maxPosition = -1;
+        Event startEvent = null;
+        Event maxPositionEvent = null;
+        for (int ii = 0; ii < len; ii++) {
+            Event ev = mSelectedEvents.get(ii);
+            int position = ev.getColumn();
+            if (position == startPosition) {
+                startEvent = ev;
+            } else if (position > maxPosition) {
+                maxPositionEvent = ev;
+                maxPosition = position;
+            }
+            for (int jj = 0; jj < len; jj++) {
+                if (jj == ii) {
+                    continue;
+                }
+                Event neighbor = mSelectedEvents.get(jj);
+                int neighborPosition = neighbor.getColumn();
+                if (neighborPosition == position - 1) {
+                    ev.nextUp = neighbor;
+                } else if (neighborPosition == position + 1) {
+                    ev.nextDown = neighbor;
+                }
+            }
+        }
+        if (startEvent != null) {
+            mSelectedEvent = startEvent;
+        } else {
+            mSelectedEvent = maxPositionEvent;
+        }
+    }
+
+    RectF drawAllDayEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
+        // If this event is selected, then use the selection color
+        if (mSelectedEvent == event) {
+            // Also, remember the last selected event that we drew
+            mPrevSelectedEvent = event;
+            p.setColor(mSelectionColor);
+            eventTextPaint.setColor(mSelectedEventTextColor);
+        } else {
+            // Use the normal color for all-day events
+            p.setColor(event.color);
+            eventTextPaint.setColor(mEventTextColor);
+        }
+
+        RectF rf = mRectF;
+        rf.top = event.top;
+        rf.bottom = event.bottom;
+        rf.left = event.left;
+        rf.right = event.right;
+        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+        rf.left += 2;
+        rf.right -= 2;
+        return rf;
+    }
+
+    private void drawEvents(int date, int left, int top, Canvas canvas, Paint p) {
+        Paint eventTextPaint = mEventTextPaint;
+        int cellWidth = mCellWidth;
+        int cellHeight = mCellHeight;
+
+        // Use the selected hour as the selection region
+        Rect selectionArea = mRect;
+        selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
+        selectionArea.bottom = selectionArea.top + cellHeight;
+        selectionArea.left = left;
+        selectionArea.right = selectionArea.left + cellWidth;
+
+        ArrayList<Event> events = mEvents;
+        int numEvents = events.size();
+        EventGeometry geometry = mEventGeometry;
+
+        for (int i = 0; i < numEvents; i++) {
+            Event event = events.get(i);
+            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+                continue;
+            }
+
+            if (date == mSelectionDay && !mSelectionAllDay && mComputeSelectedEvents
+                    && geometry.eventIntersectsSelection(event, selectionArea)) {
+                mSelectedEvents.add(event);
+            }
+
+            RectF rf = drawEventRect(event, canvas, p, eventTextPaint);
+            drawEventText(event, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
+        }
+
+        if (date == mSelectionDay && !mSelectionAllDay && isFocused()
+                && mSelectionMode != SELECTION_HIDDEN) {
+            computeNeighbors();
+            if (mSelectedEvent != null) {
+                RectF rf = drawEventRect(mSelectedEvent, canvas, p, eventTextPaint);
+                drawEventText(mSelectedEvent, rf, canvas, eventTextPaint, NORMAL_TEXT_TOP_MARGIN);
+            }
+        }
+    }
+
+    // Computes the "nearest" neighbor event in four directions (left, right,
+    // up, down) for each of the events in the mSelectedEvents array.
+    private void computeNeighbors() {
+        int len = mSelectedEvents.size();
+        if (len == 0 || mSelectedEvent != null) {
+            return;
+        }
+
+        // First, clear all the links
+        for (int ii = 0; ii < len; ii++) {
+            Event ev = mSelectedEvents.get(ii);
+            ev.nextUp = null;
+            ev.nextDown = null;
+            ev.nextLeft = null;
+            ev.nextRight = null;
+        }
+
+        Event startEvent = mSelectedEvents.get(0);
+        int startEventDistance1 = 100000;  // any large number
+        int startEventDistance2 = 100000;  // any large number
+        int prevLocation = FROM_NONE;
+        int prevTop;
+        int prevBottom;
+        int prevLeft;
+        int prevRight;
+        int prevCenter = 0;
+        Rect box = getCurrentSelectionPosition();
+        if (mPrevSelectedEvent != null) {
+            prevTop = (int) mPrevSelectedEvent.top;
+            prevBottom = (int) mPrevSelectedEvent.bottom;
+            prevLeft = (int) mPrevSelectedEvent.left;
+            prevRight = (int) mPrevSelectedEvent.right;
+            // Check if the previously selected event intersects the previous
+            // selection box.  (The previously selected event may be from a
+            // much older selection box.)
+            if (prevTop >= mPrevBox.bottom || prevBottom <= mPrevBox.top
+                    || prevRight <= mPrevBox.left || prevLeft >= mPrevBox.right) {
+                mPrevSelectedEvent = null;
+                prevTop = mPrevBox.top;
+                prevBottom = mPrevBox.bottom;
+                prevLeft = mPrevBox.left;
+                prevRight = mPrevBox.right;
+            } else {
+                // Clip the top and bottom to the previous selection box.
+                if (prevTop < mPrevBox.top) {
+                    prevTop = mPrevBox.top;
+                }
+                if (prevBottom > mPrevBox.bottom) {
+                    prevBottom = mPrevBox.bottom;
+                }
+            }
+        } else {
+            // Just use the previously drawn selection box
+            prevTop = mPrevBox.top;
+            prevBottom = mPrevBox.bottom;
+            prevLeft = mPrevBox.left;
+            prevRight = mPrevBox.right;
+        }
+
+        // Figure out where we came from and compute the center of that area.
+        if (prevLeft >= box.right) {
+            // The previously selected event was to the right of us.
+            prevLocation = FROM_RIGHT;
+            prevCenter = (prevTop + prevBottom) / 2;
+        } else if (prevRight <= box.left) {
+            // The previously selected event was to the left of us.
+            prevLocation = FROM_LEFT;
+            prevCenter = (prevTop + prevBottom) / 2;
+        } else if (prevBottom <= box.top) {
+            // The previously selected event was above us.
+            prevLocation = FROM_ABOVE;
+            prevCenter = (prevLeft + prevRight) / 2;
+        } else if (prevTop >= box.bottom) {
+            // The previously selected event was below us.
+            prevLocation = FROM_BELOW;
+            prevCenter = (prevLeft + prevRight) / 2;
+        }
+
+        // For each event in the selected event list "mSelectedEvents", search
+        // all the other events in that list for the nearest neighbor in 4
+        // directions.
+        for (int ii = 0; ii < len; ii++) {
+            Event ev = mSelectedEvents.get(ii);
+
+            int startTime = ev.startTime;
+            int endTime = ev.endTime;
+            int left = (int) ev.left;
+            int right = (int) ev.right;
+            int top = (int) ev.top;
+            if (top < box.top) {
+                top = box.top;
+            }
+            int bottom = (int) ev.bottom;
+            if (bottom > box.bottom) {
+                bottom = box.bottom;
+            }
+            if (false) {
+                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
+                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+                if (DateFormat.is24HourFormat(mContext)) {
+                    flags |= DateUtils.FORMAT_24HOUR;
+                }
+                String timeRange = DateUtils.formatDateRange(mContext,
+                        ev.startMillis, ev.endMillis, flags);
+                Log.i("Cal", "left: " + left + " right: " + right + " top: " + top
+                        + " bottom: " + bottom + " ev: " + timeRange + " " + ev.title);
+            }
+            int upDistanceMin = 10000;     // any large number
+            int downDistanceMin = 10000;   // any large number
+            int leftDistanceMin = 10000;   // any large number
+            int rightDistanceMin = 10000;  // any large number
+            Event upEvent = null;
+            Event downEvent = null;
+            Event leftEvent = null;
+            Event rightEvent = null;
+
+            // Pick the starting event closest to the previously selected event,
+            // if any.  distance1 takes precedence over distance2.
+            int distance1 = 0;
+            int distance2 = 0;
+            if (prevLocation == FROM_ABOVE) {
+                if (left >= prevCenter) {
+                    distance1 = left - prevCenter;
+                } else if (right <= prevCenter) {
+                    distance1 = prevCenter - right;
+                }
+                distance2 = top - prevBottom;
+            } else if (prevLocation == FROM_BELOW) {
+                if (left >= prevCenter) {
+                    distance1 = left - prevCenter;
+                } else if (right <= prevCenter) {
+                    distance1 = prevCenter - right;
+                }
+                distance2 = prevTop - bottom;
+            } else if (prevLocation == FROM_LEFT) {
+                if (bottom <= prevCenter) {
+                    distance1 = prevCenter - bottom;
+                } else if (top >= prevCenter) {
+                    distance1 = top - prevCenter;
+                }
+                distance2 = left - prevRight;
+            } else if (prevLocation == FROM_RIGHT) {
+                if (bottom <= prevCenter) {
+                    distance1 = prevCenter - bottom;
+                } else if (top >= prevCenter) {
+                    distance1 = top - prevCenter;
+                }
+                distance2 = prevLeft - right;
+            }
+            if (distance1 < startEventDistance1
+                    || (distance1 == startEventDistance1 && distance2 < startEventDistance2)) {
+                startEvent = ev;
+                startEventDistance1 = distance1;
+                startEventDistance2 = distance2;
+            }
+
+            // For each neighbor, figure out if it is above or below or left
+            // or right of me and compute the distance.
+            for (int jj = 0; jj < len; jj++) {
+                if (jj == ii) {
+                    continue;
+                }
+                Event neighbor = mSelectedEvents.get(jj);
+                int neighborLeft = (int) neighbor.left;
+                int neighborRight = (int) neighbor.right;
+                if (neighbor.endTime <= startTime) {
+                    // This neighbor is entirely above me.
+                    // If we overlap the same column, then compute the distance.
+                    if (neighborLeft < right && neighborRight > left) {
+                        int distance = startTime - neighbor.endTime;
+                        if (distance < upDistanceMin) {
+                            upDistanceMin = distance;
+                            upEvent = neighbor;
+                        } else if (distance == upDistanceMin) {
+                            int center = (left + right) / 2;
+                            int currentDistance = 0;
+                            int currentLeft = (int) upEvent.left;
+                            int currentRight = (int) upEvent.right;
+                            if (currentRight <= center) {
+                                currentDistance = center - currentRight;
+                            } else if (currentLeft >= center) {
+                                currentDistance = currentLeft - center;
+                            }
+
+                            int neighborDistance = 0;
+                            if (neighborRight <= center) {
+                                neighborDistance = center - neighborRight;
+                            } else if (neighborLeft >= center) {
+                                neighborDistance = neighborLeft - center;
+                            }
+                            if (neighborDistance < currentDistance) {
+                                upDistanceMin = distance;
+                                upEvent = neighbor;
+                            }
+                        }
+                    }
+                } else if (neighbor.startTime >= endTime) {
+                    // This neighbor is entirely below me.
+                    // If we overlap the same column, then compute the distance.
+                    if (neighborLeft < right && neighborRight > left) {
+                        int distance = neighbor.startTime - endTime;
+                        if (distance < downDistanceMin) {
+                            downDistanceMin = distance;
+                            downEvent = neighbor;
+                        } else if (distance == downDistanceMin) {
+                            int center = (left + right) / 2;
+                            int currentDistance = 0;
+                            int currentLeft = (int) downEvent.left;
+                            int currentRight = (int) downEvent.right;
+                            if (currentRight <= center) {
+                                currentDistance = center - currentRight;
+                            } else if (currentLeft >= center) {
+                                currentDistance = currentLeft - center;
+                            }
+
+                            int neighborDistance = 0;
+                            if (neighborRight <= center) {
+                                neighborDistance = center - neighborRight;
+                            } else if (neighborLeft >= center) {
+                                neighborDistance = neighborLeft - center;
+                            }
+                            if (neighborDistance < currentDistance) {
+                                downDistanceMin = distance;
+                                downEvent = neighbor;
+                            }
+                        }
+                    }
+                }
+
+                if (neighborLeft >= right) {
+                    // This neighbor is entirely to the right of me.
+                    // Take the closest neighbor in the y direction.
+                    int center = (top + bottom) / 2;
+                    int distance = 0;
+                    int neighborBottom = (int) neighbor.bottom;
+                    int neighborTop = (int) neighbor.top;
+                    if (neighborBottom <= center) {
+                        distance = center - neighborBottom;
+                    } else if (neighborTop >= center) {
+                        distance = neighborTop - center;
+                    }
+                    if (distance < rightDistanceMin) {
+                        rightDistanceMin = distance;
+                        rightEvent = neighbor;
+                    } else if (distance == rightDistanceMin) {
+                        // Pick the closest in the x direction
+                        int neighborDistance = neighborLeft - right;
+                        int currentDistance = (int) rightEvent.left - right;
+                        if (neighborDistance < currentDistance) {
+                            rightDistanceMin = distance;
+                            rightEvent = neighbor;
+                        }
+                    }
+                } else if (neighborRight <= left) {
+                    // This neighbor is entirely to the left of me.
+                    // Take the closest neighbor in the y direction.
+                    int center = (top + bottom) / 2;
+                    int distance = 0;
+                    int neighborBottom = (int) neighbor.bottom;
+                    int neighborTop = (int) neighbor.top;
+                    if (neighborBottom <= center) {
+                        distance = center - neighborBottom;
+                    } else if (neighborTop >= center) {
+                        distance = neighborTop - center;
+                    }
+                    if (distance < leftDistanceMin) {
+                        leftDistanceMin = distance;
+                        leftEvent = neighbor;
+                    } else if (distance == leftDistanceMin) {
+                        // Pick the closest in the x direction
+                        int neighborDistance = left - neighborRight;
+                        int currentDistance = left - (int) leftEvent.right;
+                        if (neighborDistance < currentDistance) {
+                            leftDistanceMin = distance;
+                            leftEvent = neighbor;
+                        }
+                    }
+                }
+            }
+            ev.nextUp = upEvent;
+            ev.nextDown = downEvent;
+            ev.nextLeft = leftEvent;
+            ev.nextRight = rightEvent;
+        }
+        mSelectedEvent = startEvent;
+    }
+
+
+    private RectF drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
+
+        int color = event.color;
+
+        // Fade visible boxes if event was declined.
+        boolean declined = (event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED);
+        if (declined) {
+            int alpha = color & 0xff000000;
+            color &= 0x00ffffff;
+            int red = (color & 0x00ff0000) >> 16;
+            int green = (color & 0x0000ff00) >> 8;
+            int blue = (color & 0x0000ff);
+            color = ((red >> 1) << 16) | ((green >> 1) << 8) | (blue >> 1);
+            color += 0x7F7F7F + alpha;
+        }
+
+        // If this event is selected, then use the selection color
+        if (mSelectedEvent == event) {
+            if (mSelectionMode == SELECTION_PRESSED) {
+                // Also, remember the last selected event that we drew
+                mPrevSelectedEvent = event;
+                // box = mBoxPressed;
+                p.setColor(mPressedColor); // FIXME:pressed
+                eventTextPaint.setColor(mSelectedEventTextColor);
+            } else if (mSelectionMode == SELECTION_SELECTED) {
+                // Also, remember the last selected event that we drew
+                mPrevSelectedEvent = event;
+                // box = mBoxSelected;
+                p.setColor(mSelectionColor);
+                eventTextPaint.setColor(mSelectedEventTextColor);
+            } else if (mSelectionMode == SELECTION_LONGPRESS) {
+                // box = mBoxLongPressed;
+                p.setColor(mPressedColor); // FIXME: longpressed (maybe -- this doesn't seem to work)
+                eventTextPaint.setColor(mSelectedEventTextColor);
+            } else {
+                p.setColor(color);
+                eventTextPaint.setColor(mEventTextColor);
+            }
+        } else {
+            p.setColor(color);
+            eventTextPaint.setColor(mEventTextColor);
+        }
+
+
+        RectF rf = mRectF;
+        rf.top = event.top;
+        rf.bottom = event.bottom;
+        rf.left = event.left;
+        rf.right = event.right - 1;
+
+        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+        // Draw a darker border
+        float[] hsv = new float[3];
+        Color.colorToHSV(p.getColor(), hsv);
+        hsv[1] = 1.0f;
+        hsv[2] *= 0.75f;
+        mPaintBorder.setColor(Color.HSVToColor(hsv));
+        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, mPaintBorder);
+
+        rf.left += 2;
+        rf.right -= 2;
+
+        return rf;
+    }
+
+    private Pattern drawTextSanitizerFilter = Pattern.compile("[\t\n],");
+
+    // Sanitize a string before passing it to drawText or else we get little
+    // squares. For newlines and tabs before a comma, delete the character.
+    // Otherwise, just replace them with a space.
+    private String drawTextSanitizer(String string) {
+        Matcher m = drawTextSanitizerFilter.matcher(string);
+        string = m.replaceAll(",").replace('\n', ' ').replace('\n', ' ');
+        return string;
+    }
+
+    private void drawEventText(Event event, RectF rf, Canvas canvas, Paint p, int topMargin) {
+        if (!mDrawTextInEventRect) {
+            return;
+        }
+
+        float width = rf.right - rf.left;
+        float height = rf.bottom - rf.top;
+
+        // Leave one pixel extra space between lines
+        int lineHeight = mEventTextHeight + 1;
+
+        // If the rectangle is too small for text, then return
+        if (width < MIN_CELL_WIDTH_FOR_TEXT || height <= lineHeight) {
+            return;
+        }
+
+        // Truncate the event title to a known (large enough) limit
+        String text = event.getTitleAndLocation();
+
+        text = drawTextSanitizer(text);
+
+        int len = text.length();
+        if (len > MAX_EVENT_TEXT_LEN) {
+            text = text.substring(0, MAX_EVENT_TEXT_LEN);
+            len = MAX_EVENT_TEXT_LEN;
+        }
+
+        // Figure out how much space the event title will take, and create a
+        // String fragment that will fit in the rectangle.  Use multiple lines,
+        // if available.
+        p.getTextWidths(text, mCharWidths);
+        String fragment = text;
+        float top = rf.top + mEventTextAscent + topMargin;
+        int start = 0;
+
+        // Leave one pixel extra space at the bottom
+        while (start < len && height >= (lineHeight + 1)) {
+            boolean lastLine = (height < 2 * lineHeight + 1);
+            // Skip leading spaces at the beginning of each line
+            do {
+                char c = text.charAt(start);
+                if (c != ' ') break;
+                start += 1;
+            } while (start < len);
+
+            float sum = 0;
+            int end = start;
+            for (int ii = start; ii < len; ii++) {
+                char c = text.charAt(ii);
+
+                // If we found the end of a word, then remember the ending
+                // position.
+                if (c == ' ') {
+                    end = ii;
+                }
+                sum += mCharWidths[ii];
+                // If adding this character would exceed the width and this
+                // isn't the last line, then break the line at the previous
+                // word.  If there was no previous word, then break this word.
+                if (sum > width) {
+                    if (end > start && !lastLine) {
+                        // There was a previous word on this line.
+                        fragment = text.substring(start, end);
+                        start = end;
+                        break;
+                    }
+
+                    // This is the only word and it is too long to fit on
+                    // the line (or this is the last line), so take as many
+                    // characters of this word as will fit.
+                    fragment = text.substring(start, ii);
+                    start = ii;
+                    break;
+                }
+            }
+
+            // If sum <= width, then we can fit the rest of the text on
+            // this line.
+            if (sum <= width) {
+                fragment = text.substring(start, len);
+                start = len;
+            }
+
+            canvas.drawText(fragment, rf.left + 1, top, p);
+
+            top += lineHeight;
+            height -= lineHeight;
+        }
+    }
+
+    private void updateEventDetails() {
+        if (mSelectedEvent == null || mSelectionMode == SELECTION_HIDDEN
+                || mSelectionMode == SELECTION_LONGPRESS) {
+            mPopup.dismiss();
+            return;
+        }
+        if (mLastPopupEventID == mSelectedEvent.id) {
+            return;
+        }
+
+        mLastPopupEventID = mSelectedEvent.id;
+
+        // Remove any outstanding callbacks to dismiss the popup.
+        getHandler().removeCallbacks(mDismissPopup);
+
+        Event event = mSelectedEvent;
+        TextView titleView = (TextView) mPopupView.findViewById(R.id.event_title);
+        titleView.setText(event.title);
+
+        ImageView imageView = (ImageView) mPopupView.findViewById(R.id.reminder_icon);
+        imageView.setVisibility(event.hasAlarm ? View.VISIBLE : View.GONE);
+
+        imageView = (ImageView) mPopupView.findViewById(R.id.repeat_icon);
+        imageView.setVisibility(event.isRepeating ? View.VISIBLE : View.GONE);
+
+        int flags;
+        if (event.allDay) {
+            flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE |
+                    DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
+        } else {
+            flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE
+                    | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL
+                    | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+        }
+        if (DateFormat.is24HourFormat(mContext)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        String timeRange = Utils.formatDateRange(mContext,
+                event.startMillis, event.endMillis, flags);
+        TextView timeView = (TextView) mPopupView.findViewById(R.id.time);
+        timeView.setText(timeRange);
+
+        TextView whereView = (TextView) mPopupView.findViewById(R.id.where);
+        final boolean empty = TextUtils.isEmpty(event.location);
+        whereView.setVisibility(empty ? View.GONE : View.VISIBLE);
+        if (!empty) whereView.setText(event.location);
+
+        mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, mHoursWidth, 5);
+        postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
+    }
+
+    // The following routines are called from the parent activity when certain
+    // touch events occur.
+    private void doDown(MotionEvent ev) {
+        mTouchMode = TOUCH_MODE_DOWN;
+        mViewStartX = 0;
+        mOnFlingCalled = false;
+        getHandler().removeCallbacks(mContinueScroll);
+    }
+
+    private void doSingleTapUp(MotionEvent ev) {
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        int selectedDay = mSelectionDay;
+        int selectedHour = mSelectionHour;
+
+        boolean validPosition = setSelectionFromPosition(x, y);
+        if (!validPosition) {
+            // return if the touch wasn't on an area of concern
+            return;
+        }
+
+        mSelectionMode = SELECTION_SELECTED;
+        invalidate();
+
+        boolean launchNewView = false;
+        if (mSelectedEvent != null) {
+            // If the tap is on an event, launch the "View event" view
+            mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, mSelectedEvent.id,
+                    mSelectedEvent.startMillis, mSelectedEvent.endMillis, (int) ev.getRawX(),
+                    (int) ev.getRawY());
+        } else if (selectedDay == mSelectionDay && selectedHour == mSelectionHour) {
+            // If the tap is on an already selected hour slot, then create a new
+            // event
+            mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
+                    getSelectedTimeInMillis(), 0, (int) ev.getRawX(), (int) ev.getRawY());
+        } else {
+            Time startTime = new Time(mBaseDate);
+            startTime.setJulianDay(mSelectionDay);
+            startTime.hour = mSelectionHour;
+            startTime.normalize(true /* ignore isDst */);
+
+            Time endTime = new Time(startTime);
+            endTime.hour++;
+
+            mController.sendEvent(this, EventType.GO_TO, startTime, endTime, -1, ViewType.CURRENT);
+        }
+    }
+
+    private void doLongPress(MotionEvent ev) {
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        boolean validPosition = setSelectionFromPosition(x, y);
+        if (!validPosition) {
+            // return if the touch wasn't on an area of concern
+            return;
+        }
+
+        mSelectionMode = SELECTION_LONGPRESS;
+        invalidate();
+        performLongClick();
+    }
+
+    private void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
+        // Use the distance from the current point to the initial touch instead
+        // of deltaX and deltaY to avoid accumulating floating-point rounding
+        // errors.  Also, we don't need floats, we can use ints.
+        int distanceX = (int) e1.getX() - (int) e2.getX();
+        int distanceY = (int) e1.getY() - (int) e2.getY();
+
+        // If we haven't figured out the predominant scroll direction yet,
+        // then do it now.
+        if (mTouchMode == TOUCH_MODE_DOWN) {
+            int absDistanceX = Math.abs(distanceX);
+            int absDistanceY = Math.abs(distanceY);
+            mScrollStartY = mViewStartY;
+            mPreviousDistanceX = 0;
+            mPreviousDirection = 0;
+
+            // If the x distance is at least twice the y distance, then lock
+            // the scroll horizontally.  Otherwise scroll vertically.
+            if (absDistanceX >= 2 * absDistanceY) {
+                mTouchMode = TOUCH_MODE_HSCROLL;
+                mViewStartX = distanceX;
+                initNextView(-mViewStartX);
+            } else {
+                mTouchMode = TOUCH_MODE_VSCROLL;
+            }
+        } else if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
+            // We are already scrolling horizontally, so check if we
+            // changed the direction of scrolling so that the other week
+            // is now visible.
+            mViewStartX = distanceX;
+            if (distanceX != 0) {
+                int direction = (distanceX > 0) ? 1 : -1;
+                if (direction != mPreviousDirection) {
+                    // The user has switched the direction of scrolling
+                    // so re-init the next view
+                    initNextView(-mViewStartX);
+                    mPreviousDirection = direction;
+                }
+            }
+
+            // If we have moved at least the HORIZONTAL_SCROLL_THRESHOLD,
+            // then change the title to the new day (or week), but only
+            // if we haven't already changed the title.
+            if (distanceX >= HORIZONTAL_SCROLL_THRESHOLD) {
+                if (mPreviousDistanceX < HORIZONTAL_SCROLL_THRESHOLD) {
+                    DayView view = (DayView) mViewSwitcher.getNextView();
+                    mTitleTextView.setText(view.mDateRange);
+                }
+            } else if (distanceX <= -HORIZONTAL_SCROLL_THRESHOLD) {
+                if (mPreviousDistanceX > -HORIZONTAL_SCROLL_THRESHOLD) {
+                    DayView view = (DayView) mViewSwitcher.getNextView();
+                    mTitleTextView.setText(view.mDateRange);
+                }
+            } else {
+                if (mPreviousDistanceX >= HORIZONTAL_SCROLL_THRESHOLD
+                        || mPreviousDistanceX <= -HORIZONTAL_SCROLL_THRESHOLD) {
+                    mTitleTextView.setText(mDateRange);
+                }
+            }
+            mPreviousDistanceX = distanceX;
+        }
+
+        if ((mTouchMode & TOUCH_MODE_VSCROLL) != 0) {
+            mViewStartY = mScrollStartY + distanceY;
+            if (mViewStartY < 0) {
+                mViewStartY = 0;
+            } else if (mViewStartY > mMaxViewStartY) {
+                mViewStartY = mMaxViewStartY;
+            }
+            computeFirstHour();
+        }
+
+        mScrolling = true;
+
+        if (mSelectionMode != SELECTION_HIDDEN) {
+            mSelectionMode = SELECTION_HIDDEN;
+        }
+        invalidate();
+    }
+
+    private void doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        mTouchMode = TOUCH_MODE_INITIAL_STATE;
+        mSelectionMode = SELECTION_HIDDEN;
+        mOnFlingCalled = true;
+        int deltaX = (int) e2.getX() - (int) e1.getX();
+        int distanceX = Math.abs(deltaX);
+        int deltaY = (int) e2.getY() - (int) e1.getY();
+        int distanceY = Math.abs(deltaY);
+
+        if ((distanceX >= HORIZONTAL_SCROLL_THRESHOLD) && (distanceX > distanceY)) {
+            boolean switchForward = initNextView(deltaX);
+            DayView view = (DayView) mViewSwitcher.getNextView();
+            mTitleTextView.setText(view.mDateRange);
+
+            Time end = new Time(view.mBaseDate);
+            end.monthDay += mNumDays;
+            end.normalize(true);
+            Log.d(TAG, "doFling");
+            mController
+                    .sendEvent(this, EventType.GO_TO, view.mBaseDate, end, -1, ViewType.CURRENT);
+
+            mViewStartX = 0;
+            return;
+        }
+
+        // Continue scrolling vertically
+        mContinueScroll.init((int) velocityY / 20);
+        post(mContinueScroll);
+    }
+
+    private boolean initNextView(int deltaX) {
+        // Change the view to the previous day or week
+        DayView view = (DayView) mViewSwitcher.getNextView();
+        Time date = view.mBaseDate;
+        date.set(mBaseDate);
+        boolean switchForward;
+        if (deltaX > 0) {
+            date.monthDay -= mNumDays;
+            view.mSelectionDay = mSelectionDay - mNumDays;
+            switchForward = false;
+        } else {
+            date.monthDay += mNumDays;
+            view.mSelectionDay = mSelectionDay + mNumDays;
+            switchForward = true;
+        }
+        date.normalize(true /* ignore isDst */);
+        initView(view);
+        view.layout(getLeft(), getTop(), getRight(), getBottom());
+        view.reloadEvents();
+        return switchForward;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            mGestureDetector.onTouchEvent(ev);
+            return true;
+
+        case MotionEvent.ACTION_MOVE:
+            mGestureDetector.onTouchEvent(ev);
+            return true;
+
+        case MotionEvent.ACTION_UP:
+            mGestureDetector.onTouchEvent(ev);
+            if (mOnFlingCalled) {
+                return true;
+            }
+            if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
+                mTouchMode = TOUCH_MODE_INITIAL_STATE;
+                if (Math.abs(mViewStartX) > HORIZONTAL_SCROLL_THRESHOLD) {
+                    // The user has gone beyond the threshold so switch views
+                    switchViews(mViewStartX > 0, mViewStartX, mViewWidth);
+                    mViewStartX = 0;
+                    return true;
+                } else {
+                    // Not beyond the threshold so invalidate which will cause
+                    // the view to snap back.  Also call recalc() to ensure
+                    // that we have the correct starting date and title.
+                    recalc();
+                    mTitleTextView.setText(mDateRange);
+                    invalidate();
+                    mViewStartX = 0;
+                }
+            }
+
+            // If we were scrolling, then reset the selected hour so that it
+            // is visible.
+            if (mScrolling) {
+                mScrolling = false;
+                resetSelectedHour();
+                invalidate();
+            }
+            return true;
+
+        // This case isn't expected to happen.
+        case MotionEvent.ACTION_CANCEL:
+            mGestureDetector.onTouchEvent(ev);
+            mScrolling = false;
+            resetSelectedHour();
+            return true;
+
+        default:
+            if (mGestureDetector.onTouchEvent(ev)) {
+                return true;
+            }
+            return super.onTouchEvent(ev);
+        }
+    }
+
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        MenuItem item;
+
+        // If the trackball is held down, then the context menu pops up and
+        // we never get onKeyUp() for the long-press.  So check for it here
+        // and change the selection to the long-press state.
+        if (mSelectionMode != SELECTION_LONGPRESS) {
+            mSelectionMode = SELECTION_LONGPRESS;
+            invalidate();
+        }
+
+        final long startMillis = getSelectedTimeInMillis();
+        int flags = DateUtils.FORMAT_SHOW_TIME
+                | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
+                | DateUtils.FORMAT_SHOW_WEEKDAY;
+        final String title = DateUtils.formatDateTime(mContext, startMillis, flags);
+        menu.setHeaderTitle(title);
+
+        int numSelectedEvents = mSelectedEvents.size();
+        if (mNumDays == 1) {
+            // Day view.
+
+            // If there is a selected event, then allow it to be viewed and
+            // edited.
+            if (numSelectedEvents >= 1) {
+                item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view);
+                item.setOnMenuItemClickListener(mContextMenuHandler);
+                item.setIcon(android.R.drawable.ic_menu_info_details);
+
+                int accessLevel = getEventAccessLevel(mContext, mSelectedEvent);
+                if (accessLevel == ACCESS_LEVEL_EDIT) {
+                    item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit);
+                    item.setOnMenuItemClickListener(mContextMenuHandler);
+                    item.setIcon(android.R.drawable.ic_menu_edit);
+                    item.setAlphabeticShortcut('e');
+                }
+
+                if (accessLevel >= ACCESS_LEVEL_DELETE) {
+                    item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete);
+                    item.setOnMenuItemClickListener(mContextMenuHandler);
+                    item.setIcon(android.R.drawable.ic_menu_delete);
+                }
+
+                item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
+                item.setOnMenuItemClickListener(mContextMenuHandler);
+                item.setIcon(android.R.drawable.ic_menu_add);
+                item.setAlphabeticShortcut('n');
+            } else {
+                // Otherwise, if the user long-pressed on a blank hour, allow
+                // them to create an event.  They can also do this by tapping.
+                item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
+                item.setOnMenuItemClickListener(mContextMenuHandler);
+                item.setIcon(android.R.drawable.ic_menu_add);
+                item.setAlphabeticShortcut('n');
+            }
+        } else {
+            // Week view.
+
+            // If there is a selected event, then allow it to be viewed and
+            // edited.
+            if (numSelectedEvents >= 1) {
+                item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view);
+                item.setOnMenuItemClickListener(mContextMenuHandler);
+                item.setIcon(android.R.drawable.ic_menu_info_details);
+
+                int accessLevel = getEventAccessLevel(mContext, mSelectedEvent);
+                if (accessLevel == ACCESS_LEVEL_EDIT) {
+                    item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit);
+                    item.setOnMenuItemClickListener(mContextMenuHandler);
+                    item.setIcon(android.R.drawable.ic_menu_edit);
+                    item.setAlphabeticShortcut('e');
+                }
+
+                if (accessLevel >= ACCESS_LEVEL_DELETE) {
+                    item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete);
+                    item.setOnMenuItemClickListener(mContextMenuHandler);
+                    item.setIcon(android.R.drawable.ic_menu_delete);
+                }
+            }
+
+            item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
+            item.setOnMenuItemClickListener(mContextMenuHandler);
+            item.setIcon(android.R.drawable.ic_menu_add);
+            item.setAlphabeticShortcut('n');
+
+            item = menu.add(0, MENU_DAY, 0, R.string.show_day_view);
+            item.setOnMenuItemClickListener(mContextMenuHandler);
+            item.setIcon(android.R.drawable.ic_menu_day);
+            item.setAlphabeticShortcut('d');
+
+            item = menu.add(0, MENU_AGENDA, 0, R.string.show_agenda_view);
+            item.setOnMenuItemClickListener(mContextMenuHandler);
+            item.setIcon(android.R.drawable.ic_menu_agenda);
+            item.setAlphabeticShortcut('a');
+        }
+
+        mPopup.dismiss();
+    }
+
+    private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
+        public boolean onMenuItemClick(MenuItem item) {
+            switch (item.getItemId()) {
+                case MENU_EVENT_VIEW: {
+                    if (mSelectedEvent != null) {
+                        mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT,
+                                mSelectedEvent.id, mSelectedEvent.startMillis,
+                                mSelectedEvent.endMillis, 0, 0);
+                    }
+                    break;
+                }
+                case MENU_EVENT_EDIT: {
+                    if (mSelectedEvent != null) {
+                        mController.sendEventRelatedEvent(this, EventType.EDIT_EVENT,
+                                mSelectedEvent.id, mSelectedEvent.startMillis,
+                                mSelectedEvent.endMillis, 0, 0);
+                    }
+                    break;
+                }
+                case MENU_DAY: {
+                    mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1,
+                            ViewType.DAY);
+                    break;
+                }
+                case MENU_AGENDA: {
+                    mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1,
+                            ViewType.AGENDA);
+                    break;
+                }
+                case MENU_EVENT_CREATE: {
+                    long startMillis = getSelectedTimeInMillis();
+                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
+                    mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
+                            startMillis, endMillis, 0, 0);
+                    break;
+                }
+                case MENU_EVENT_DELETE: {
+                    if (mSelectedEvent != null) {
+                        Event selectedEvent = mSelectedEvent;
+                        long begin = selectedEvent.startMillis;
+                        long end = selectedEvent.endMillis;
+                        long id = selectedEvent.id;
+                        mController.sendEventRelatedEvent(this, EventType.DELETE_EVENT, id, begin,
+                                end, 0, 0);
+                    }
+                    break;
+                }
+                default: {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private static int getEventAccessLevel(Context context, Event e) {
+        ContentResolver cr = context.getContentResolver();
+
+        int visibility = Calendars.NO_ACCESS;
+        int relationship = Attendees.RELATIONSHIP_ORGANIZER;
+
+        // Get the calendar id for this event
+        Cursor cursor = cr.query(ContentUris.withAppendedId(Events.CONTENT_URI, e.id),
+                new String[] { Events.CALENDAR_ID },
+                null /* selection */,
+                null /* selectionArgs */,
+                null /* sort */);
+
+        if (cursor == null) {
+            return ACCESS_LEVEL_NONE;
+        }
+
+        if (cursor.getCount() == 0) {
+            cursor.close();
+            return ACCESS_LEVEL_NONE;
+        }
+
+        cursor.moveToFirst();
+        long calId = cursor.getLong(0);
+        cursor.close();
+
+        Uri uri = Calendars.CONTENT_URI;
+        String where = String.format(CALENDARS_WHERE, calId);
+        cursor = cr.query(uri, CALENDARS_PROJECTION, where, null, null);
+
+        String calendarOwnerAccount = null;
+        if (cursor != null) {
+            cursor.moveToFirst();
+            visibility = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL);
+            calendarOwnerAccount = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+            cursor.close();
+        }
+
+        if (visibility < Calendars.CONTRIBUTOR_ACCESS) {
+            return ACCESS_LEVEL_NONE;
+        }
+
+        if (e.guestsCanModify) {
+            return ACCESS_LEVEL_EDIT;
+        }
+
+        if (!TextUtils.isEmpty(calendarOwnerAccount) &&
+                calendarOwnerAccount.equalsIgnoreCase(e.organizer)) {
+            return ACCESS_LEVEL_EDIT;
+        }
+
+        return ACCESS_LEVEL_DELETE;
+    }
+
+    /**
+     * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
+     * If the touch position is not within the displayed grid, then this
+     * method returns false.
+     *
+     * @param x the x position of the touch
+     * @param y the y position of the touch
+     * @return true if the touch position is valid
+     */
+    private boolean setSelectionFromPosition(int x, int y) {
+        if (x < mHoursWidth) {
+            return false;
+        }
+
+        int day = (x - mHoursWidth) / (mCellWidth + DAY_GAP);
+        if (day >= mNumDays) {
+            day = mNumDays - 1;
+        }
+        day += mFirstJulianDay;
+        int hour;
+        if (y < mFirstCell + mFirstHourOffset) {
+            mSelectionAllDay = true;
+        } else {
+            hour = (y - mFirstCell - mFirstHourOffset) / (mCellHeight + HOUR_GAP);
+            hour += mFirstHour;
+            mSelectionHour = hour;
+            mSelectionAllDay = false;
+        }
+        mSelectionDay = day;
+        findSelectedEvent(x, y);
+//        Log.i("Cal", "setSelectionFromPosition( " + x + ", " + y + " ) day: " + day
+//                + " hour: " + hour
+//                + " mFirstCell: " + mFirstCell + " mFirstHourOffset: " + mFirstHourOffset);
+//        if (mSelectedEvent != null) {
+//            Log.i("Cal", "  num events: " + mSelectedEvents.size() + " event: " + mSelectedEvent.title);
+//            for (Event ev : mSelectedEvents) {
+//                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
+//                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+//                String timeRange = formatDateRange(mContext,
+//                        ev.startMillis, ev.endMillis, flags);
+//
+//                Log.i("Cal", "  " + timeRange + " " + ev.title);
+//            }
+//        }
+        return true;
+    }
+
+    private void findSelectedEvent(int x, int y) {
+        int date = mSelectionDay;
+        int cellWidth = mCellWidth;
+        ArrayList<Event> events = mEvents;
+        int numEvents = events.size();
+        int left = mHoursWidth + (mSelectionDay - mFirstJulianDay) * (cellWidth + DAY_GAP);
+        int top = 0;
+        mSelectedEvent = null;
+
+        mSelectedEvents.clear();
+        if (mSelectionAllDay) {
+            float yDistance;
+            float minYdistance = 10000.0f;  // any large number
+            Event closestEvent = null;
+            float drawHeight = mAllDayHeight;
+            int yOffset = mBannerPlusMargin + ALLDAY_TOP_MARGIN;
+            for (int i = 0; i < numEvents; i++) {
+                Event event = events.get(i);
+                if (!event.allDay) {
+                    continue;
+                }
+
+                if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
+                    float numRectangles = event.getMaxColumns();
+                    float height = drawHeight / numRectangles;
+                    if (height > MAX_ALLDAY_EVENT_HEIGHT) {
+                        height = MAX_ALLDAY_EVENT_HEIGHT;
+                    }
+                    float eventTop = yOffset + height * event.getColumn();
+                    float eventBottom = eventTop + height;
+                    if (eventTop < y && eventBottom > y) {
+                        // If the touch is inside the event rectangle, then
+                        // add the event.
+                        mSelectedEvents.add(event);
+                        closestEvent = event;
+                        break;
+                    } else {
+                        // Find the closest event
+                        if (eventTop >= y) {
+                            yDistance = eventTop - y;
+                        } else {
+                            yDistance = y - eventBottom;
+                        }
+                        if (yDistance < minYdistance) {
+                            minYdistance = yDistance;
+                            closestEvent = event;
+                        }
+                    }
+                }
+            }
+            mSelectedEvent = closestEvent;
+            return;
+        }
+
+        // Adjust y for the scrollable bitmap
+        y += mViewStartY - mFirstCell;
+
+        // Use a region around (x,y) for the selection region
+        Rect region = mRect;
+        region.left = x - 10;
+        region.right = x + 10;
+        region.top = y - 10;
+        region.bottom = y + 10;
+
+        EventGeometry geometry = mEventGeometry;
+
+        for (int i = 0; i < numEvents; i++) {
+            Event event = events.get(i);
+            // Compute the event rectangle.
+            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
+                continue;
+            }
+
+            // If the event intersects the selection region, then add it to
+            // mSelectedEvents.
+            if (geometry.eventIntersectsSelection(event, region)) {
+                mSelectedEvents.add(event);
+            }
+        }
+
+        // If there are any events in the selected region, then assign the
+        // closest one to mSelectedEvent.
+        if (mSelectedEvents.size() > 0) {
+            int len = mSelectedEvents.size();
+            Event closestEvent = null;
+            float minDist = mViewWidth + mViewHeight;  // some large distance
+            for (int index = 0; index < len; index++) {
+                Event ev = mSelectedEvents.get(index);
+                float dist = geometry.pointToEvent(x, y, ev);
+                if (dist < minDist) {
+                    minDist = dist;
+                    closestEvent = ev;
+                }
+            }
+            mSelectedEvent = closestEvent;
+
+            // Keep the selected hour and day consistent with the selected
+            // event.  They could be different if we touched on an empty hour
+            // slot very close to an event in the previous hour slot.  In
+            // that case we will select the nearby event.
+            int startDay = mSelectedEvent.startDay;
+            int endDay = mSelectedEvent.endDay;
+            if (mSelectionDay < startDay) {
+                mSelectionDay = startDay;
+            } else if (mSelectionDay > endDay) {
+                mSelectionDay = endDay;
+            }
+
+            int startHour = mSelectedEvent.startTime / 60;
+            int endHour;
+            if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
+                endHour = (mSelectedEvent.endTime - 1) / 60;
+            } else {
+                endHour = mSelectedEvent.endTime / 60;
+            }
+
+            if (mSelectionHour < startHour) {
+                mSelectionHour = startHour;
+            } else if (mSelectionHour > endHour) {
+                mSelectionHour = endHour;
+            }
+        }
+    }
+
+    // Encapsulates the code to continue the scrolling after the
+    // finger is lifted.  Instead of stopping the scroll immediately,
+    // the scroll continues to "free spin" and gradually slows down.
+    private class ContinueScroll implements Runnable {
+        int mSignDeltaY;
+        int mAbsDeltaY;
+        float mFloatDeltaY;
+        long mFreeSpinTime;
+        private static final float FRICTION_COEF = 0.7F;
+        private static final long FREE_SPIN_MILLIS = 180;
+        private static final int MAX_DELTA = 60;
+        private static final int SCROLL_REPEAT_INTERVAL = 30;
+
+        public void init(int deltaY) {
+            mSignDeltaY = 0;
+            if (deltaY > 0) {
+                mSignDeltaY = 1;
+            } else if (deltaY < 0) {
+                mSignDeltaY = -1;
+            }
+            mAbsDeltaY = Math.abs(deltaY);
+
+            // Limit the maximum speed
+            if (mAbsDeltaY > MAX_DELTA) {
+                mAbsDeltaY = MAX_DELTA;
+            }
+            mFloatDeltaY = mAbsDeltaY;
+            mFreeSpinTime = System.currentTimeMillis() + FREE_SPIN_MILLIS;
+//            Log.i("Cal", "init scroll: mAbsDeltaY: " + mAbsDeltaY
+//                    + " mViewStartY: " + mViewStartY);
+        }
+
+        public void run() {
+            long time = System.currentTimeMillis();
+
+            // Start out with a frictionless "free spin"
+            if (time > mFreeSpinTime) {
+                // If the delta is small, then apply a fixed deceleration.
+                // Otherwise
+                if (mAbsDeltaY <= 10) {
+                    mAbsDeltaY -= 2;
+                } else {
+                    mFloatDeltaY *= FRICTION_COEF;
+                    mAbsDeltaY = (int) mFloatDeltaY;
+                }
+
+                if (mAbsDeltaY < 0) {
+                    mAbsDeltaY = 0;
+                }
+            }
+
+            if (mSignDeltaY == 1) {
+                mViewStartY -= mAbsDeltaY;
+            } else {
+                mViewStartY += mAbsDeltaY;
+            }
+//            Log.i("Cal", "  scroll: mAbsDeltaY: " + mAbsDeltaY
+//                    + " mViewStartY: " + mViewStartY);
+
+            if (mViewStartY < 0) {
+                mViewStartY = 0;
+                mAbsDeltaY = 0;
+            } else if (mViewStartY > mMaxViewStartY) {
+                mViewStartY = mMaxViewStartY;
+                mAbsDeltaY = 0;
+            }
+
+            computeFirstHour();
+
+            if (mAbsDeltaY > 0) {
+                postDelayed(this, SCROLL_REPEAT_INTERVAL);
+            } else {
+                // Done scrolling.
+                mScrolling = false;
+                resetSelectedHour();
+            }
+
+            invalidate();
+        }
+    }
+
+    /**
+     * Cleanup the pop-up and timers.
+     */
+    public void cleanup() {
+        // Protect against null-pointer exceptions
+        if (mPopup != null) {
+            mPopup.dismiss();
+        }
+        mLastPopupEventID = INVALID_EVENT_ID;
+        Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeCallbacks(mDismissPopup);
+            handler.removeCallbacks(mUpdateCurrentTime);
+        }
+
+        // Turn off redraw
+        mRemeasure = false;
+    }
+
+    /**
+     * Restart the update timer
+     */
+    public void restartCurrentTimeUpdates() {
+        post(mUpdateCurrentTime);
+    }
+
+    @Override protected void onDetachedFromWindow() {
+        cleanup();
+        if (mBitmap != null) {
+            mBitmap.recycle();
+            mBitmap = null;
+        }
+        super.onDetachedFromWindow();
+    }
+
+    class DismissPopup implements Runnable {
+        public void run() {
+            // Protect against null-pointer exceptions
+            if (mPopup != null) {
+                mPopup.dismiss();
+            }
+        }
+    }
+
+    class UpdateCurrentTime implements Runnable {
+        public void run() {
+            long currentTime = System.currentTimeMillis();
+            mCurrentTime.set(currentTime);
+            //% causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
+            postDelayed(mUpdateCurrentTime,
+                    UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
+            mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
+            invalidate();
+        }
+    }
+
+    class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public boolean onSingleTapUp(MotionEvent ev) {
+            DayView.this.doSingleTapUp(ev);
+            return true;
+        }
+
+        @Override
+        public void onLongPress(MotionEvent ev) {
+            DayView.this.doLongPress(ev);
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+            DayView.this.doScroll(e1, e2, distanceX, distanceY);
+            return true;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            DayView.this.doFling(e1, e2, velocityX, velocityY);
+            return true;
+        }
+
+        @Override
+        public boolean onDown(MotionEvent ev) {
+            DayView.this.doDown(ev);
+            return true;
+        }
     }
 }
+
diff --git a/src/com/android/calendar/DeleteEventHelper.java b/src/com/android/calendar/DeleteEventHelper.java
index 2a86a77..60a3ef2 100644
--- a/src/com/android/calendar/DeleteEventHelper.java
+++ b/src/com/android/calendar/DeleteEventHelper.java
@@ -16,11 +16,13 @@
 
 package com.android.calendar;
 
+import com.android.calendar.event.EditEventHelper;
+
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.database.Cursor;
 import android.net.Uri;
@@ -53,11 +55,11 @@
  */
 public class DeleteEventHelper {
     private final Activity mParent;
-    private final ContentResolver mContentResolver;
+    private Context mContext;
 
     private long mStartMillis;
     private long mEndMillis;
-    private Cursor mCursor;
+    private CalendarEventModel mModel;
 
     /**
      * If true, then call finish() on the parent activity when done.
@@ -68,34 +70,45 @@
      * These are the corresponding indices into the array of strings
      * "R.array.delete_repeating_labels" in the resource file.
      */
-    static final int DELETE_SELECTED = 0;
-    static final int DELETE_ALL_FOLLOWING = 1;
-    static final int DELETE_ALL = 2;
+    public static final int DELETE_SELECTED = 0;
+    public static final int DELETE_ALL_FOLLOWING = 1;
+    public static final int DELETE_ALL = 2;
 
     private int mWhichDelete;
     private AlertDialog mAlertDialog;
 
-    private static final String[] EVENT_PROJECTION = new String[] {
-        Events._ID,
-        Events.TITLE,
-        Events.ALL_DAY,
-        Events.CALENDAR_ID,
-        Events.RRULE,
-        Events.DTSTART,
-        Events._SYNC_ID,
-        Events.EVENT_TIMEZONE,
-    };
-
-    private int mEventIndexId;
-    private int mEventIndexRrule;
     private String mSyncId;
 
-    public DeleteEventHelper(Activity parent, boolean exitWhenDone) {
-        mParent = parent;
-        mContentResolver = mParent.getContentResolver();
+    private AsyncQueryService mService;
+
+    public DeleteEventHelper(Context context, Activity parentActivity, boolean exitWhenDone) {
+        if (exitWhenDone && parentActivity == null) {
+            throw new IllegalArgumentException("parentActivity is required to exit when done");
+        }
+
+        mContext = context;
+        mParent = parentActivity;
+        // TODO move the creation of this service out into the activity.
+        mService = new AsyncQueryService(mContext) {
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                if (cursor == null) {
+                    return;
+                }
+                cursor.moveToFirst();
+                CalendarEventModel mModel = new CalendarEventModel();
+                EditEventHelper.setModelFromCursor(mModel, cursor);
+                cursor.close();
+                DeleteEventHelper.this.delete(mStartMillis, mEndMillis, mModel, mWhichDelete);
+            }
+        };
         mExitWhenDone = exitWhenDone;
     }
 
+    public void setAsyncQueryService(AsyncQueryService service) {
+        mService = service;
+    }
+
     public void setExitWhenDone(boolean exitWhenDone) {
         mExitWhenDone = exitWhenDone;
     }
@@ -106,9 +119,22 @@
     private DialogInterface.OnClickListener mDeleteNormalDialogListener =
             new DialogInterface.OnClickListener() {
         public void onClick(DialogInterface dialog, int button) {
-            long id = mCursor.getInt(mEventIndexId);
+            long id = mModel.mId; // mCursor.getInt(mEventIndexId);
             Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
-            mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
+            mService.startDelete(mService.getNextToken(), null, uri, null, null, Utils.UNDO_DELAY);
+            if (mExitWhenDone) {
+                mParent.finish();
+            }
+        }
+    };
+
+    /**
+     * This callback is used when an exception to an event is deleted
+     */
+    private DialogInterface.OnClickListener mDeleteExceptionDialogListener =
+        new DialogInterface.OnClickListener() {
+        public void onClick(DialogInterface dialog, int button) {
+            deleteExceptionEvent();
             if (mExitWhenDone) {
                 mParent.finish();
             }
@@ -158,18 +184,18 @@
      */
     public void delete(long begin, long end, long eventId, int which) {
         Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
-        Cursor cursor = mParent.managedQuery(uri, EVENT_PROJECTION, null, null, null);
-        if (cursor == null) {
-            return;
-        }
-        cursor.moveToFirst();
-        delete(begin, end, cursor, which);
+        mService.startQuery(mService.getNextToken(), null, uri, EditEventHelper.EVENT_PROJECTION,
+                null, null, null);
+        mStartMillis = begin;
+        mEndMillis = end;
+        mWhichDelete = which;
     }
 
     /**
      * Does the required processing for deleting an event.  This method
-     * takes a {@link Cursor} object as a parameter, which must point to
-     * a row in the Events table containing the required database fields.
+     * takes a {@link CalendarEventModel} object, which must have a valid
+     * uri for referencing the event in the database and have the required
+     * fields listed below.
      * The required fields for a normal event are:
      *
      * <ul>
@@ -189,43 +215,60 @@
      *   <li> Events.EVENT_TIMEZONE </li>
      * </ul>
      *
+     * If the event no longer exists in the db this will still prompt
+     * the user but will return without modifying the db after the query
+     * returns.
+     *
      * @param begin the begin time of the event, in UTC milliseconds
      * @param end the end time of the event, in UTC milliseconds
      * @param cursor the database cursor containing the required fields
      * @param which one of the values {@link DELETE_SELECTED},
      *  {@link DELETE_ALL_FOLLOWING}, {@link DELETE_ALL}, or -1
      */
-    public void delete(long begin, long end, Cursor cursor, int which) {
+    public void delete(long begin, long end, CalendarEventModel model, int which) {
         mWhichDelete = which;
         mStartMillis = begin;
         mEndMillis = end;
-        mCursor = cursor;
-        mEventIndexId = mCursor.getColumnIndexOrThrow(Events._ID);
-        mEventIndexRrule = mCursor.getColumnIndexOrThrow(Events.RRULE);
-        int eventIndexSyncId = mCursor.getColumnIndexOrThrow(Events._SYNC_ID);
-        mSyncId = mCursor.getString(eventIndexSyncId);
+        mModel = model;
+        mSyncId = model.mSyncId;
 
         // If this is a repeating event, then pop up a dialog asking the
         // user if they want to delete all of the repeating events or
         // just some of them.
-        String rRule = mCursor.getString(mEventIndexRrule);
+        String rRule = model.mRrule;
+        String originalEvent = model.mOriginalEvent;
         if (rRule == null) {
-            // This is a normal event. Pop up a confirmation dialog.
-            new AlertDialog.Builder(mParent)
+            AlertDialog dialog = new AlertDialog.Builder(mContext)
             .setTitle(R.string.delete_title)
             .setMessage(R.string.delete_this_event_title)
             .setIcon(android.R.drawable.ic_dialog_alert)
-            .setPositiveButton(android.R.string.ok, mDeleteNormalDialogListener)
             .setNegativeButton(android.R.string.cancel, null)
-            .show();
+            .create();
+
+            if (originalEvent == null) {
+                // This is a normal event. Pop up a confirmation dialog.
+                dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                        mContext.getText(android.R.string.ok),
+                        mDeleteNormalDialogListener);
+            } else {
+                // This is an exception event. Pop up a confirmation dialog.
+                dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                        mContext.getText(android.R.string.ok),
+                        mDeleteExceptionDialogListener);
+            }
+            dialog.show();
         } else {
             // This is a repeating event.  Pop up a dialog asking which events
             // to delete.
             int labelsArrayId = R.array.delete_repeating_labels;
             if (mSyncId == null) {
                 labelsArrayId = R.array.delete_repeating_labels_no_selected;
+                if (which > 0) {
+                    which--;
+                    mWhichDelete--;
+                }
             }
-            AlertDialog dialog = new AlertDialog.Builder(mParent)
+            AlertDialog dialog = new AlertDialog.Builder(mContext)
             .setTitle(R.string.delete_title)
             .setIcon(android.R.drawable.ic_dialog_alert)
             .setSingleChoiceItems(labelsArrayId, which, mDeleteListListener)
@@ -243,17 +286,23 @@
         }
     }
 
-    private void deleteRepeatingEvent(int which) {
-        int indexDtstart = mCursor.getColumnIndexOrThrow(Events.DTSTART);
-        int indexAllDay = mCursor.getColumnIndexOrThrow(Events.ALL_DAY);
-        int indexTitle = mCursor.getColumnIndexOrThrow(Events.TITLE);
-        int indexTimezone = mCursor.getColumnIndexOrThrow(Events.EVENT_TIMEZONE);
-        int indexCalendarId = mCursor.getColumnIndexOrThrow(Events.CALENDAR_ID);
+    private void deleteExceptionEvent() {
+        long id = mModel.mId; // mCursor.getInt(mEventIndexId);
 
-        String rRule = mCursor.getString(mEventIndexRrule);
-        boolean allDay = mCursor.getInt(indexAllDay) != 0;
-        long dtstart = mCursor.getLong(indexDtstart);
-        long id = mCursor.getInt(mEventIndexId);
+        // update a recurrence exception by setting its status to "canceled"
+        ContentValues values = new ContentValues();
+        values.put(Events.STATUS, Events.STATUS_CANCELED);
+
+        Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
+        mService.startUpdate(mService.getNextToken(), null, uri, values, null, null,
+                Utils.UNDO_DELAY);
+    }
+
+    private void deleteRepeatingEvent(int which) {
+        String rRule = mModel.mRrule;
+        boolean allDay = mModel.mAllDay;
+        long dtstart = mModel.mStart;
+        long id = mModel.mId; // mCursor.getInt(mEventIndexId);
 
         // If the repeating event has not been given a sync id from the server
         // yet, then we can't delete a single instance of this event.  (This is
@@ -267,8 +316,7 @@
         }
 
         switch (which) {
-            case DELETE_SELECTED:
-            {
+            case DELETE_SELECTED: {
                 // If we are deleting the first event in the series, then
                 // instead of creating a recurrence exception, just change
                 // the start time of the recurrence.
@@ -282,11 +330,11 @@
 
                 // The title might not be necessary, but it makes it easier
                 // to find this entry in the database when there is a problem.
-                String title = mCursor.getString(indexTitle);
+                String title = mModel.mTitle;
                 values.put(Events.TITLE, title);
 
-                String timezone = mCursor.getString(indexTimezone);
-                int calendarId = mCursor.getInt(indexCalendarId);
+                String timezone = mModel.mTimezone;
+                long calendarId = mModel.mCalendarId;
                 values.put(Events.EVENT_TIMEZONE, timezone);
                 values.put(Events.ALL_DAY, allDay ? 1 : 0);
                 values.put(Events.CALENDAR_ID, calendarId);
@@ -296,12 +344,14 @@
                 values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
                 values.put(Events.STATUS, Events.STATUS_CANCELED);
 
-                mContentResolver.insert(Events.CONTENT_URI, values);
+                mService.startInsert(mService.getNextToken(), null, Events.CONTENT_URI, values,
+                        Utils.UNDO_DELAY);
                 break;
             }
             case DELETE_ALL: {
                 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
-                mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
+                mService.startDelete(mService.getNextToken(), null, uri, null, null,
+                        Utils.UNDO_DELAY);
                 break;
             }
             case DELETE_ALL_FOLLOWING: {
@@ -309,7 +359,8 @@
                 // following events, then delete them all.
                 if (dtstart == mStartMillis) {
                     Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
-                    mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
+                    mService.startDelete(mService.getNextToken(), null, uri, null, null,
+                            Utils.UNDO_DELAY);
                     break;
                 }
 
@@ -333,7 +384,8 @@
                 values.put(Events.DTSTART, dtstart);
                 values.put(Events.RRULE, eventRecurrence.toString());
                 Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
-                mContentResolver.update(uri, values, null, null);
+                mService.startUpdate(mService.getNextToken(), null, uri, values, null, null,
+                        Utils.UNDO_DELAY);
                 break;
             }
         }
diff --git a/src/com/android/calendar/EditEvent.java b/src/com/android/calendar/EditEvent.java
deleted file mode 100644
index 31b6059..0000000
--- a/src/com/android/calendar/EditEvent.java
+++ /dev/null
@@ -1,2335 +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.calendar;
-
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import static android.provider.Calendar.EVENT_END_TIME;
-
-import com.android.calendar.TimezoneAdapter.TimezoneRow;
-import com.android.common.Rfc822InputFilter;
-import com.android.common.Rfc822Validator;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.DatePickerDialog;
-import android.app.DatePickerDialog.OnDateSetListener;
-import android.app.ProgressDialog;
-import android.app.TimePickerDialog;
-import android.app.TimePickerDialog.OnTimeSetListener;
-import android.content.AsyncQueryHandler;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderOperation.Builder;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.pim.EventRecurrence;
-import android.provider.Calendar.Attendees;
-import android.provider.Calendar.Calendars;
-import android.provider.Calendar.Events;
-import android.provider.Calendar.Reminders;
-import android.text.Editable;
-import android.text.InputFilter;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.DatePicker;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.MultiAutoCompleteTextView;
-import android.widget.ResourceCursorAdapter;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.TimePicker;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Formatter;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Locale;
-import java.util.TimeZone;
-
-public class EditEvent extends Activity implements View.OnClickListener,
-        DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
-    private static final String TAG = "EditEvent";
-    private static final boolean DEBUG = false;
-
-    /**
-     * This is the symbolic name for the key used to pass in the boolean
-     * for creating all-day events that is part of the extra data of the intent.
-     * This is used only for creating new events and is set to true if
-     * the default for the new event should be an all-day event.
-     */
-    public static final String EVENT_ALL_DAY = "allDay";
-
-    private static final int MAX_REMINDERS = 5;
-
-    private static final int MENU_GROUP_REMINDER = 1;
-    private static final int MENU_GROUP_SHOW_OPTIONS = 2;
-    private static final int MENU_GROUP_HIDE_OPTIONS = 3;
-
-    private static final int MENU_ADD_REMINDER = 1;
-    private static final int MENU_SHOW_EXTRA_OPTIONS = 2;
-    private static final int MENU_HIDE_EXTRA_OPTIONS = 3;
-
-    private static final String[] EVENT_PROJECTION = new String[] {
-            Events._ID,               // 0
-            Events.TITLE,             // 1
-            Events.DESCRIPTION,       // 2
-            Events.EVENT_LOCATION,    // 3
-            Events.ALL_DAY,           // 4
-            Events.HAS_ALARM,         // 5
-            Events.CALENDAR_ID,       // 6
-            Events.DTSTART,           // 7
-            Events.DURATION,          // 8
-            Events.EVENT_TIMEZONE,    // 9
-            Events.RRULE,             // 10
-            Events._SYNC_ID,          // 11
-            Events.TRANSPARENCY,      // 12
-            Events.VISIBILITY,        // 13
-            Events.OWNER_ACCOUNT,     // 14
-            Events.HAS_ATTENDEE_DATA, // 15
-    };
-    private static final int EVENT_INDEX_ID = 0;
-    private static final int EVENT_INDEX_TITLE = 1;
-    private static final int EVENT_INDEX_DESCRIPTION = 2;
-    private static final int EVENT_INDEX_EVENT_LOCATION = 3;
-    private static final int EVENT_INDEX_ALL_DAY = 4;
-    private static final int EVENT_INDEX_HAS_ALARM = 5;
-    private static final int EVENT_INDEX_CALENDAR_ID = 6;
-    private static final int EVENT_INDEX_DTSTART = 7;
-    private static final int EVENT_INDEX_DURATION = 8;
-    private static final int EVENT_INDEX_TIMEZONE = 9;
-    private static final int EVENT_INDEX_RRULE = 10;
-    private static final int EVENT_INDEX_SYNC_ID = 11;
-    private static final int EVENT_INDEX_TRANSPARENCY = 12;
-    private static final int EVENT_INDEX_VISIBILITY = 13;
-    private static final int EVENT_INDEX_OWNER_ACCOUNT = 14;
-    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 15;
-
-    private static final String[] CALENDARS_PROJECTION = new String[] {
-            Calendars._ID,           // 0
-            Calendars.DISPLAY_NAME,  // 1
-            Calendars.OWNER_ACCOUNT, // 2
-            Calendars.COLOR,         // 3
-    };
-    private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
-    private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
-    private static final int CALENDARS_INDEX_COLOR = 3;
-    private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
-            Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SYNC_EVENTS + "=1";
-
-    private static final String[] REMINDERS_PROJECTION = new String[] {
-            Reminders._ID,      // 0
-            Reminders.MINUTES,  // 1
-    };
-    private static final int REMINDERS_INDEX_MINUTES = 1;
-    private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
-            Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
-            Reminders.METHOD_DEFAULT + ")";
-
-    private static final String[] ATTENDEES_PROJECTION = new String[] {
-        Attendees.ATTENDEE_NAME,            // 0
-        Attendees.ATTENDEE_EMAIL,           // 1
-    };
-    private static final int ATTENDEES_INDEX_NAME = 0;
-    private static final int ATTENDEES_INDEX_EMAIL = 1;
-    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND "
-            + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
-    private static final String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND " +
-            Attendees.ATTENDEE_EMAIL + " IN (";
-
-    private static final int DOES_NOT_REPEAT = 0;
-    private static final int REPEATS_DAILY = 1;
-    private static final int REPEATS_EVERY_WEEKDAY = 2;
-    private static final int REPEATS_WEEKLY_ON_DAY = 3;
-    private static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
-    private static final int REPEATS_MONTHLY_ON_DAY = 5;
-    private static final int REPEATS_YEARLY = 6;
-    private static final int REPEATS_CUSTOM = 7;
-
-    private static final int MODIFY_UNINITIALIZED = 0;
-    private static final int MODIFY_SELECTED = 1;
-    private static final int MODIFY_ALL = 2;
-    private static final int MODIFY_ALL_FOLLOWING = 3;
-
-    private static final int DAY_IN_SECONDS = 24 * 60 * 60;
-
-    private int mFirstDayOfWeek; // cached in onCreate
-    private Uri mUri;
-    private Cursor mEventCursor;
-    private Cursor mCalendarsCursor;
-
-    private Button mStartDateButton;
-    private Button mEndDateButton;
-    private Button mStartTimeButton;
-    private Button mEndTimeButton;
-    private Button mSaveButton;
-    private Button mDeleteButton;
-    private Button mDiscardButton;
-    private Button mTimezoneButton;
-    private CheckBox mAllDayCheckBox;
-    private Spinner mCalendarsSpinner;
-    private Spinner mRepeatsSpinner;
-    private Spinner mAvailabilitySpinner;
-    private Spinner mVisibilitySpinner;
-    private TextView mTitleTextView;
-    private TextView mLocationTextView;
-    private TextView mDescriptionTextView;
-    private TextView mTimezoneTextView;
-    private TextView mTimezoneFooterView;
-    private TextView mStartTimeHome;
-    private TextView mStartDateHome;
-    private TextView mEndTimeHome;
-    private TextView mEndDateHome;
-    private View mRemindersSeparator;
-    private LinearLayout mRemindersContainer;
-    private LinearLayout mExtraOptions;
-    private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
-    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
-    private Rfc822Validator mEmailValidator;
-    private MultiAutoCompleteTextView mAttendeesList;
-    private EmailAddressAdapter mAddressAdapter;
-    private TimezoneAdapter mTimezoneAdapter;
-    private String mOriginalAttendees = "";
-
-    // Used to control the visibility of the Guests textview. Default to true
-    private boolean mHasAttendeeData = true;
-
-    private EventRecurrence mEventRecurrence = new EventRecurrence();
-    private String mRrule;
-    private boolean mCalendarsQueryComplete;
-    private boolean mSaveAfterQueryComplete;
-    private ProgressDialog mLoadingCalendarsDialog;
-    private AlertDialog mNoCalendarsDialog;
-    private AlertDialog mTimezoneDialog;
-    private ContentValues mInitialValues;
-    private String mOwnerAccount;
-
-    /**
-     * If the repeating event is created on the phone and it hasn't been
-     * synced yet to the web server, then there is a bug where you can't
-     * delete or change an instance of the repeating event.  This case
-     * can be detected with mSyncId.  If mSyncId == null, then the repeating
-     * event has not been synced to the phone, in which case we won't allow
-     * the user to change one instance.
-     */
-    private String mSyncId;
-
-    private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer> (0);
-    private ArrayList<Integer> mReminderValues;
-    private ArrayList<String> mReminderLabels;
-
-    private Time mStartTime;
-    private Time mEndTime;
-    private String mTimezone;
-    private int mModification = MODIFY_UNINITIALIZED;
-    private int mDefaultReminderMinutes;
-
-    private DeleteEventHelper mDeleteEventHelper;
-    private QueryHandler mQueryHandler;
-
-    private static StringBuilder mSB = new StringBuilder(50);
-    private static Formatter mF = new Formatter(mSB, Locale.getDefault());
-
-    // This is here in case we need to update tz info later
-    private Runnable mUpdateTZ = null;
-
-    /* This class is used to update the time buttons. */
-    private class TimeListener implements OnTimeSetListener {
-        private View mView;
-
-        public TimeListener(View view) {
-            mView = view;
-        }
-
-        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
-            // Cache the member variables locally to avoid inner class overhead.
-            Time startTime = mStartTime;
-            Time endTime = mEndTime;
-
-            // Cache the start and end millis so that we limit the number
-            // of calls to normalize() and toMillis(), which are fairly
-            // expensive.
-            long startMillis;
-            long endMillis;
-            if (mView == mStartTimeButton) {
-                // The start time was changed.
-                int hourDuration = endTime.hour - startTime.hour;
-                int minuteDuration = endTime.minute - startTime.minute;
-
-                startTime.hour = hourOfDay;
-                startTime.minute = minute;
-                startMillis = startTime.normalize(true);
-
-                // Also update the end time to keep the duration constant.
-                endTime.hour = hourOfDay + hourDuration;
-                endTime.minute = minute + minuteDuration;
-            } else {
-                // The end time was changed.
-                startMillis = startTime.toMillis(true);
-                endTime.hour = hourOfDay;
-                endTime.minute = minute;
-
-                // Move to the next day if the end time is before the start time.
-                if (endTime.before(startTime)) {
-                    endTime.monthDay = startTime.monthDay + 1;
-                }
-            }
-
-            endMillis = endTime.normalize(true);
-
-            setDate(mEndDateButton, endMillis);
-            setTime(mStartTimeButton, startMillis);
-            setTime(mEndTimeButton, endMillis);
-            updateHomeTime();
-        }
-    }
-
-    private class TimeClickListener implements View.OnClickListener {
-        private Time mTime;
-
-        public TimeClickListener(Time time) {
-            mTime = time;
-        }
-
-        public void onClick(View v) {
-            new TimePickerDialog(EditEvent.this, new TimeListener(v),
-                    mTime.hour, mTime.minute,
-                    DateFormat.is24HourFormat(EditEvent.this)).show();
-        }
-    }
-
-    private class DateListener implements OnDateSetListener {
-        View mView;
-
-        public DateListener(View view) {
-            mView = view;
-        }
-
-        public void onDateSet(DatePicker view, int year, int month, int monthDay) {
-            // Cache the member variables locally to avoid inner class overhead.
-            Time startTime = mStartTime;
-            Time endTime = mEndTime;
-
-            // Cache the start and end millis so that we limit the number
-            // of calls to normalize() and toMillis(), which are fairly
-            // expensive.
-            long startMillis;
-            long endMillis;
-            if (mView == mStartDateButton) {
-                // The start date was changed.
-                int yearDuration = endTime.year - startTime.year;
-                int monthDuration = endTime.month - startTime.month;
-                int monthDayDuration = endTime.monthDay - startTime.monthDay;
-
-                startTime.year = year;
-                startTime.month = month;
-                startTime.monthDay = monthDay;
-                startMillis = startTime.normalize(true);
-
-                // Also update the end date to keep the duration constant.
-                endTime.year = year + yearDuration;
-                endTime.month = month + monthDuration;
-                endTime.monthDay = monthDay + monthDayDuration;
-                endMillis = endTime.normalize(true);
-
-                // If the start date has changed then update the repeats.
-                populateRepeats();
-            } else {
-                // The end date was changed.
-                startMillis = startTime.toMillis(true);
-                endTime.year = year;
-                endTime.month = month;
-                endTime.monthDay = monthDay;
-                endMillis = endTime.normalize(true);
-
-                // Do not allow an event to have an end time before the start time.
-                if (endTime.before(startTime)) {
-                    endTime.set(startTime);
-                    endMillis = startMillis;
-                }
-            }
-
-            setDate(mStartDateButton, startMillis);
-            setDate(mEndDateButton, endMillis);
-            setTime(mEndTimeButton, endMillis); // In case end time had to be reset
-            updateHomeTime();
-        }
-    }
-
-    private class DateClickListener implements View.OnClickListener {
-        private Time mTime;
-
-        public DateClickListener(Time time) {
-            mTime = time;
-        }
-
-        public void onClick(View v) {
-            new DatePickerDialog(EditEvent.this, new DateListener(v), mTime.year,
-                    mTime.month, mTime.monthDay).show();
-        }
-    }
-
-    static private class CalendarsAdapter extends ResourceCursorAdapter {
-        public CalendarsAdapter(Context context, Cursor c) {
-            super(context, R.layout.calendars_item, c);
-            setDropDownViewResource(R.layout.calendars_dropdown_item);
-        }
-
-        @Override
-        public void bindView(View view, Context context, Cursor cursor) {
-            View colorBar = view.findViewById(R.id.color);
-            if (colorBar != null) {
-                colorBar.setBackgroundDrawable(
-                        Utils.getColorChip(cursor.getInt(CALENDARS_INDEX_COLOR)));
-            }
-
-            TextView name = (TextView) view.findViewById(R.id.calendar_name);
-            if (name != null) {
-                String displayName = cursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
-                name.setText(displayName);
-                name.setTextColor(0xFF000000);
-
-                TextView accountName = (TextView) view.findViewById(R.id.account_name);
-                if(accountName != null) {
-                    Resources res = context.getResources();
-                    accountName.setText(cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT));
-                    accountName.setVisibility(TextView.VISIBLE);
-                    accountName.setTextColor(res.getColor(R.color.calendar_owner_text_color));
-                }
-            }
-        }
-    }
-
-    // This is called if the user clicks on one of the buttons: "Save",
-    // "Discard", or "Delete".  This is also called if the user clicks
-    // on the "remove reminder" button.
-    public void onClick(View v) {
-        if (v == mSaveButton) {
-            if (save()) {
-                finish();
-            }
-            return;
-        }
-
-        if (v == mDeleteButton) {
-            long begin = mStartTime.toMillis(false /* use isDst */);
-            long end = mEndTime.toMillis(false /* use isDst */);
-            int which = -1;
-            switch (mModification) {
-            case MODIFY_SELECTED:
-                which = DeleteEventHelper.DELETE_SELECTED;
-                break;
-            case MODIFY_ALL_FOLLOWING:
-                which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
-                break;
-            case MODIFY_ALL:
-                which = DeleteEventHelper.DELETE_ALL;
-                break;
-            }
-            mDeleteEventHelper.delete(begin, end, mEventCursor, which);
-            return;
-        }
-
-        if (v == mDiscardButton) {
-            finish();
-            return;
-        }
-
-        // This must be a click on one of the "remove reminder" buttons
-        LinearLayout reminderItem = (LinearLayout) v.getParent();
-        LinearLayout parent = (LinearLayout) reminderItem.getParent();
-        parent.removeView(reminderItem);
-        mReminderItems.remove(reminderItem);
-        updateRemindersVisibility();
-    }
-
-    // This is called if the user cancels a popup dialog.  There are two
-    // dialogs: the "Loading calendars" dialog, and the "No calendars"
-    // dialog.  The "Loading calendars" dialog is shown if there is a delay
-    // in loading the calendars (needed when creating an event) and the user
-    // tries to save the event before the calendars have finished loading.
-    // The "No calendars" dialog is shown if there are no syncable calendars.
-    public void onCancel(DialogInterface dialog) {
-        if (dialog == mLoadingCalendarsDialog) {
-            mSaveAfterQueryComplete = false;
-        } else if (dialog == mNoCalendarsDialog) {
-            finish();
-        }
-    }
-
-    // This is called if the user clicks on a dialog button.
-    public void onClick(DialogInterface dialog, int which) {
-        if (dialog == mNoCalendarsDialog) {
-            finish();
-        } else if (dialog == mTimezoneDialog) {
-            if (which >= 0 && which < mTimezoneAdapter.getCount()) {
-                setTimezone(which);
-                updateHomeTime();
-                dialog.dismiss();
-            }
-        }
-    }
-
-    private class QueryHandler extends AsyncQueryHandler {
-        public QueryHandler(ContentResolver cr) {
-            super(cr);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            // If the query didn't return a cursor for some reason return
-            if (cursor == null) {
-                return;
-            }
-
-            // If the Activity is finishing, then close the cursor.
-            // Otherwise, use the new cursor in the adapter.
-            if (isFinishing()) {
-                stopManagingCursor(cursor);
-                cursor.close();
-            } else {
-                mCalendarsCursor = cursor;
-                startManagingCursor(cursor);
-
-                // Stop the spinner
-                getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
-                        Window.PROGRESS_VISIBILITY_OFF);
-
-                // If there are no syncable calendars, then we cannot allow
-                // creating a new event.
-                if (cursor.getCount() == 0) {
-                    // Cancel the "loading calendars" dialog if it exists
-                    if (mSaveAfterQueryComplete) {
-                        mLoadingCalendarsDialog.cancel();
-                    }
-
-                    // Create an error message for the user that, when clicked,
-                    // will exit this activity without saving the event.
-                    AlertDialog.Builder builder = new AlertDialog.Builder(EditEvent.this);
-                    builder.setTitle(R.string.no_syncable_calendars)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.no_calendars_found)
-                        .setPositiveButton(android.R.string.ok, EditEvent.this)
-                        .setOnCancelListener(EditEvent.this);
-                    mNoCalendarsDialog = builder.show();
-                    return;
-                }
-
-                int defaultCalendarPosition = findDefaultCalendarPosition(mCalendarsCursor);
-
-                // populate the calendars spinner
-                CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor);
-                mCalendarsSpinner.setAdapter(adapter);
-                mCalendarsSpinner.setSelection(defaultCalendarPosition);
-                mCalendarsQueryComplete = true;
-                if (mSaveAfterQueryComplete) {
-                    mLoadingCalendarsDialog.cancel();
-                    save();
-                    finish();
-                }
-
-                // Find user domain and set it to the validator.
-                // TODO: we may want to update this validator if the user actually picks
-                // a different calendar.  maybe not.  depends on what we want for the
-                // user experience.  this may change when we add support for multiple
-                // accounts, anyway.
-                if (mHasAttendeeData && cursor.moveToPosition(defaultCalendarPosition)) {
-                    String ownEmail = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-                    if (ownEmail != null) {
-                        String domain = extractDomain(ownEmail);
-                        if (domain != null) {
-                            mEmailValidator = new Rfc822Validator(domain);
-                            mAttendeesList.setValidator(mEmailValidator);
-                        }
-                    }
-                }
-            }
-        }
-
-        // Find the calendar position in the cursor that matches calendar in preference
-        private int findDefaultCalendarPosition(Cursor calendarsCursor) {
-            if (calendarsCursor.getCount() <= 0) {
-                return -1;
-            }
-
-            String defaultCalendar = Utils.getSharedPreference(EditEvent.this,
-                    CalendarPreferenceActivity.KEY_DEFAULT_CALENDAR, null);
-
-            if (defaultCalendar == null) {
-                return 0;
-            }
-
-            int position = 0;
-            calendarsCursor.moveToPosition(-1);
-            while(calendarsCursor.moveToNext()) {
-                if (defaultCalendar.equals(mCalendarsCursor
-                        .getString(CALENDARS_INDEX_OWNER_ACCOUNT))) {
-                    return position;
-                }
-                position++;
-            }
-            return 0;
-        }
-    }
-
-    private static String extractDomain(String email) {
-        int separator = email.lastIndexOf('@');
-        if (separator != -1 && ++separator < email.length()) {
-            return email.substring(separator);
-        }
-        return null;
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-        setContentView(R.layout.edit_event);
-
-        boolean newEvent = false;
-
-        mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
-
-        mStartTime = new Time();
-        mEndTime = new Time();
-        mTimezone = Utils.getTimeZone(this, mUpdateTZ);
-
-        Intent intent = getIntent();
-        mUri = intent.getData();
-
-        if (mUri != null) {
-            mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null, null);
-            if (mEventCursor == null || mEventCursor.getCount() == 0) {
-                // The cursor is empty. This can happen if the event was deleted.
-                finish();
-                return;
-            }
-        }
-
-        long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
-        long end = intent.getLongExtra(EVENT_END_TIME, 0);
-
-        String domain = "gmail.com";
-
-        boolean allDay = false;
-        if (mEventCursor != null) {
-            // The event already exists so fetch the all-day status
-            mEventCursor.moveToFirst();
-            mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
-            allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
-            String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
-            if (!allDay) {
-                // only load the event timezone for non-all-day events
-                // otherwise it defaults to device default
-                mTimezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
-            }
-            long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
-            mOwnerAccount = mEventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
-            if (!TextUtils.isEmpty(mOwnerAccount)) {
-                String ownerDomain = extractDomain(mOwnerAccount);
-                if (ownerDomain != null) {
-                    domain = ownerDomain;
-                }
-            }
-
-            // Remember the initial values
-            mInitialValues = new ContentValues();
-            mInitialValues.put(EVENT_BEGIN_TIME, begin);
-            mInitialValues.put(EVENT_END_TIME, end);
-            mInitialValues.put(Events.ALL_DAY, allDay ? 1 : 0);
-            mInitialValues.put(Events.RRULE, rrule);
-            mInitialValues.put(Events.EVENT_TIMEZONE, mTimezone);
-            mInitialValues.put(Events.CALENDAR_ID, calendarId);
-        } else {
-            newEvent = true;
-            // We are creating a new event, so set the default from the
-            // intent (if specified).
-            allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
-
-            // Start the spinner
-            getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
-                    Window.PROGRESS_VISIBILITY_ON);
-
-            // Start a query in the background to read the list of calendars
-            mQueryHandler = new QueryHandler(getContentResolver());
-            mQueryHandler.startQuery(0, null, Calendars.CONTENT_URI, CALENDARS_PROJECTION,
-                    CALENDARS_WHERE, null /* selection args */, null /* sort order */);
-        }
-
-        mTimezoneAdapter = new TimezoneAdapter(this, mTimezone);
-
-        // If the event is all-day, read the times in UTC timezone
-        if (begin != 0) {
-            if (allDay) {
-                mStartTime.timezone = Time.TIMEZONE_UTC;
-                mStartTime.set(begin);
-                mStartTime.timezone = mTimezone;
-
-                // Calling normalize to calculate isDst
-                mStartTime.normalize(true);
-            } else {
-                mStartTime.timezone = mTimezone;
-                mStartTime.set(begin);
-            }
-        }
-
-        if (end != 0) {
-            if (allDay) {
-                mEndTime.timezone = Time.TIMEZONE_UTC;
-                mEndTime.set(end);
-                mEndTime.timezone = mTimezone;
-
-                // Calling normalize to calculate isDst
-                mEndTime.normalize(true);
-            } else {
-                mEndTime.timezone = mTimezone;
-                mEndTime.set(end);
-            }
-        }
-
-        LayoutInflater inflater = getLayoutInflater();
-
-        // cache all the widgets
-        mTitleTextView = (TextView) findViewById(R.id.title);
-        mLocationTextView = (TextView) findViewById(R.id.location);
-        mDescriptionTextView = (TextView) findViewById(R.id.description);
-        mTimezoneTextView = (TextView) findViewById(R.id.timezone_label);
-        mTimezoneFooterView = (TextView) inflater.inflate(R.layout.timezone_footer, null);
-        mStartDateButton = (Button) findViewById(R.id.start_date);
-        mEndDateButton = (Button) findViewById(R.id.end_date);
-        mStartTimeButton = (Button) findViewById(R.id.start_time);
-        mEndTimeButton = (Button) findViewById(R.id.end_time);
-        mStartTimeHome = (TextView) findViewById(R.id.start_time_home);
-        mStartDateHome = (TextView) findViewById(R.id.start_date_home);
-        mEndTimeHome = (TextView) findViewById(R.id.end_time_home);
-        mEndDateHome = (TextView) findViewById(R.id.end_date_home);
-        mAllDayCheckBox = (CheckBox) findViewById(R.id.is_all_day);
-        mTimezoneButton = (Button) findViewById(R.id.timezone);
-        mCalendarsSpinner = (Spinner) findViewById(R.id.calendars);
-        mRepeatsSpinner = (Spinner) findViewById(R.id.repeats);
-        mAvailabilitySpinner = (Spinner) findViewById(R.id.availability);
-        mVisibilitySpinner = (Spinner) findViewById(R.id.visibility);
-        mRemindersSeparator = findViewById(R.id.reminders_separator);
-        mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container);
-        mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
-
-        if (mHasAttendeeData) {
-            mAddressAdapter = new EmailAddressAdapter(this);
-            mEmailValidator = new Rfc822Validator(domain);
-            mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees);
-        } else {
-            findViewById(R.id.attendees_group).setVisibility(View.GONE);
-        }
-
-        mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-                if (isChecked) {
-                    if (mEndTime.hour == 0 && mEndTime.minute == 0) {
-                        mEndTime.monthDay--;
-                        long endMillis = mEndTime.normalize(true);
-
-                        // Do not allow an event to have an end time before the start time.
-                        if (mEndTime.before(mStartTime)) {
-                            mEndTime.set(mStartTime);
-                            endMillis = mEndTime.normalize(true);
-                        }
-                        setDate(mEndDateButton, endMillis);
-                        setTime(mEndTimeButton, endMillis);
-                    }
-
-                    mStartTimeButton.setVisibility(View.GONE);
-                    mEndTimeButton.setVisibility(View.GONE);
-                    mTimezoneButton.setVisibility(View.GONE);
-                    mTimezoneTextView.setVisibility(View.GONE);
-                } else {
-                    if (mEndTime.hour == 0 && mEndTime.minute == 0) {
-                        mEndTime.monthDay++;
-                        long endMillis = mEndTime.normalize(true);
-                        setDate(mEndDateButton, endMillis);
-                        setTime(mEndTimeButton, endMillis);
-                    }
-
-                    mStartTimeButton.setVisibility(View.VISIBLE);
-                    mEndTimeButton.setVisibility(View.VISIBLE);
-                    mTimezoneButton.setVisibility(View.VISIBLE);
-                    mTimezoneTextView.setVisibility(View.VISIBLE);
-                }
-                updateHomeTime();
-            }
-        });
-
-        if (allDay) {
-            mAllDayCheckBox.setChecked(true);
-        } else {
-            mAllDayCheckBox.setChecked(false);
-        }
-
-        mSaveButton = (Button) findViewById(R.id.save);
-        mSaveButton.setOnClickListener(this);
-
-        mDeleteButton = (Button) findViewById(R.id.delete);
-        mDeleteButton.setOnClickListener(this);
-
-        mDiscardButton = (Button) findViewById(R.id.discard);
-        mDiscardButton.setOnClickListener(this);
-
-        // Initialize the reminder values array.
-        Resources r = getResources();
-        String[] strings = r.getStringArray(R.array.reminder_minutes_values);
-        int size = strings.length;
-        ArrayList<Integer> list = new ArrayList<Integer>(size);
-        for (int i = 0 ; i < size ; i++) {
-            list.add(Integer.parseInt(strings[i]));
-        }
-        mReminderValues = list;
-        String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
-        mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
-
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
-        String durationString =
-                prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
-        mDefaultReminderMinutes = Integer.parseInt(durationString);
-
-        if (newEvent && mDefaultReminderMinutes != 0) {
-            addReminder(this, this, mReminderItems, mReminderValues,
-                    mReminderLabels, mDefaultReminderMinutes);
-        }
-
-        long eventId = (mEventCursor == null) ? -1 : mEventCursor.getLong(EVENT_INDEX_ID);
-        ContentResolver cr = getContentResolver();
-
-        // Reminders cursor
-        boolean hasAlarm = (mEventCursor != null)
-                && (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0);
-        if (hasAlarm) {
-            Uri uri = Reminders.CONTENT_URI;
-            String where = String.format(REMINDERS_WHERE, eventId);
-            Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
-            try {
-                // First pass: collect all the custom reminder minutes (e.g.,
-                // a reminder of 8 minutes) into a global list.
-                while (reminderCursor.moveToNext()) {
-                    int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
-                    EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes);
-                }
-
-                // Second pass: create the reminder spinners
-                reminderCursor.moveToPosition(-1);
-                while (reminderCursor.moveToNext()) {
-                    int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
-                    mOriginalMinutes.add(minutes);
-                    EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
-                            mReminderLabels, minutes);
-                }
-            } finally {
-                reminderCursor.close();
-            }
-        }
-        updateRemindersVisibility();
-
-        // Setup the + Add Reminder Button
-        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
-            public void onClick(View v) {
-                addReminder();
-            }
-        };
-        ImageButton reminderRemoveButton = (ImageButton) findViewById(R.id.reminder_add);
-        reminderRemoveButton.setOnClickListener(addReminderOnClickListener);
-
-        mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
-
-       // Attendees cursor
-        if (mHasAttendeeData && eventId != -1) {
-            Uri uri = Attendees.CONTENT_URI;
-            String[] whereArgs = {Long.toString(eventId)};
-            Cursor attendeeCursor = cr.query(uri, ATTENDEES_PROJECTION, ATTENDEES_WHERE, whereArgs,
-                    null);
-            try {
-                StringBuilder b = new StringBuilder();
-                while (attendeeCursor.moveToNext()) {
-                    String name = attendeeCursor.getString(ATTENDEES_INDEX_NAME);
-                    String email = attendeeCursor.getString(ATTENDEES_INDEX_EMAIL);
-                    if (email != null) {
-                        if (name != null && name.length() > 0 && !name.equals(email)) {
-                            b.append('"').append(name).append("\" ");
-                        }
-                        b.append('<').append(email).append(">, ");
-                    }
-                }
-                if (b.length() > 0) {
-                    mOriginalAttendees = b.toString();
-                    mAttendeesList.setText(mOriginalAttendees);
-                }
-            } finally {
-                attendeeCursor.close();
-            }
-        }
-        if (mEventCursor == null) {
-            // Allow the intent to specify the fields in the event.
-            // This will allow other apps to create events easily.
-            initFromIntent(intent);
-        }
-    }
-
-    private LinkedHashSet<Rfc822Token> getAddressesFromList(MultiAutoCompleteTextView list) {
-        list.clearComposingText();
-        LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>();
-        Rfc822Tokenizer.tokenize(list.getText(), addresses);
-
-        // validate the emails, out of paranoia.  they should already be
-        // validated on input, but drop any invalid emails just to be safe.
-        Iterator<Rfc822Token> addressIterator = addresses.iterator();
-        while (addressIterator.hasNext()) {
-            Rfc822Token address = addressIterator.next();
-            if (!mEmailValidator.isValid(address.getAddress())) {
-                Log.w(TAG, "Dropping invalid attendee email address: " + address);
-                addressIterator.remove();
-            }
-        }
-        return addresses;
-    }
-
-    // From com.google.android.gm.ComposeActivity
-    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) {
-        MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) findViewById(res);
-        list.setAdapter(mAddressAdapter);
-        list.setTokenizer(new Rfc822Tokenizer());
-        list.setValidator(mEmailValidator);
-
-        // NOTE: assumes no other filters are set
-        list.setFilters(sRecipientFilters);
-
-        return list;
-    }
-
-    /**
-     * From com.google.android.gm.ComposeActivity
-     * Implements special address cleanup rules:
-     * The first space key entry following an "@" symbol that is followed by any combination
-     * of letters and symbols, including one+ dots and zero commas, should insert an extra
-     * comma (followed by the space).
-     */
-    private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
-
-    private void initFromIntent(Intent intent) {
-        String title = intent.getStringExtra(Events.TITLE);
-        if (title != null) {
-            mTitleTextView.setText(title);
-        }
-
-        String location = intent.getStringExtra(Events.EVENT_LOCATION);
-        if (location != null) {
-            mLocationTextView.setText(location);
-        }
-
-        String description = intent.getStringExtra(Events.DESCRIPTION);
-        if (description != null) {
-            mDescriptionTextView.setText(description);
-        }
-
-        int availability = intent.getIntExtra(Events.TRANSPARENCY, -1);
-        if (availability != -1) {
-            mAvailabilitySpinner.setSelection(availability);
-        }
-
-        int visibility = intent.getIntExtra(Events.VISIBILITY, -1);
-        if (visibility != -1) {
-            mVisibilitySpinner.setSelection(visibility);
-        }
-
-        String rrule = intent.getStringExtra(Events.RRULE);
-        if (rrule != null) {
-            mRrule = rrule;
-            mEventRecurrence.parse(rrule);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (mUri != null) {
-            if (mEventCursor == null || mEventCursor.getCount() == 0) {
-                // The cursor is empty. This can happen if the event was deleted.
-                finish();
-                return;
-            }
-        }
-
-        if (mEventCursor != null) {
-            Cursor cursor = mEventCursor;
-            cursor.moveToFirst();
-
-            mRrule = cursor.getString(EVENT_INDEX_RRULE);
-            String title = cursor.getString(EVENT_INDEX_TITLE);
-            String description = cursor.getString(EVENT_INDEX_DESCRIPTION);
-            String location = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
-            int availability = cursor.getInt(EVENT_INDEX_TRANSPARENCY);
-            int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
-            if (visibility > 0) {
-                // For now we the array contains the values 0, 2, and 3. We subtract one to match.
-                visibility--;
-            }
-
-            if (!TextUtils.isEmpty(mRrule) && mModification == MODIFY_UNINITIALIZED) {
-                // If this event has not been synced, then don't allow deleting
-                // or changing a single instance.
-                mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
-                mEventRecurrence.parse(mRrule);
-
-                // If we haven't synced this repeating event yet, then don't
-                // allow the user to change just one instance.
-                int itemIndex = 0;
-                CharSequence[] items;
-                if (mSyncId == null) {
-                    if(isFirstEventInSeries()) {
-                        // Still display the option so the user knows all events are changing
-                        items = new CharSequence[1];
-                    } else {
-                        items = new CharSequence[2];
-                    }
-                } else {
-                    if(isFirstEventInSeries()) {
-                        items = new CharSequence[2];
-                    } else {
-                        items = new CharSequence[3];
-                    }
-                    items[itemIndex++] = getText(R.string.modify_event);
-                }
-                items[itemIndex++] = getText(R.string.modify_all);
-
-                // Do one more check to make sure this remains at the end of the list
-                if(!isFirstEventInSeries()) {
-                    // TODO Find out why modify all following causes a dup of the first event if
-                    // it's operating on the first event.
-                    items[itemIndex++] = getText(R.string.modify_all_following);
-                }
-
-                // Display the modification dialog.
-                new AlertDialog.Builder(this)
-                        .setOnCancelListener(new OnCancelListener() {
-                            public void onCancel(DialogInterface dialog) {
-                                finish();
-                            }
-                        })
-                        .setTitle(R.string.edit_event_label)
-                        .setItems(items, new OnClickListener() {
-                            public void onClick(DialogInterface dialog, int which) {
-                                if (which == 0) {
-                                    mModification =
-                                            (mSyncId == null) ? MODIFY_ALL : MODIFY_SELECTED;
-                                } else if (which == 1) {
-                                    mModification =
-                                        (mSyncId == null) ? MODIFY_ALL_FOLLOWING : MODIFY_ALL;
-                                } else if (which == 2) {
-                                    mModification = MODIFY_ALL_FOLLOWING;
-                                }
-
-                                // If we are modifying all the events in a
-                                // series then disable and ignore the date.
-                                if (mModification == MODIFY_ALL) {
-                                    mStartDateButton.setEnabled(false);
-                                    mEndDateButton.setEnabled(false);
-                                } else if (mModification == MODIFY_SELECTED) {
-                                    mRepeatsSpinner.setEnabled(false);
-                                }
-                            }
-                        })
-                        .show();
-            }
-
-            mTitleTextView.setText(title);
-            mLocationTextView.setText(location);
-            mDescriptionTextView.setText(description);
-            mAvailabilitySpinner.setSelection(availability);
-            mVisibilitySpinner.setSelection(visibility);
-
-            // This is an existing event so hide the calendar spinner
-            // since we can't change the calendar.
-            View calendarGroup = findViewById(R.id.calendar_group);
-            calendarGroup.setVisibility(View.GONE);
-        } else {
-            // New event
-            if (Time.isEpoch(mStartTime) && Time.isEpoch(mEndTime)) {
-                mStartTime.setToNow();
-
-                // Round the time to the nearest half hour.
-                mStartTime.second = 0;
-                int minute = mStartTime.minute;
-                if (minute == 0) {
-                    // We are already on a half hour increment
-                } else if (minute > 0 && minute <= 30) {
-                    mStartTime.minute = 30;
-                } else {
-                    mStartTime.minute = 0;
-                    mStartTime.hour += 1;
-                }
-
-                long startMillis = mStartTime.normalize(true /* ignore isDst */);
-                mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS);
-            }
-
-            // Hide delete button
-            mDeleteButton.setVisibility(View.GONE);
-        }
-
-        updateRemindersVisibility();
-        populateWhen();
-        populateTimezone();
-        updateHomeTime();
-        populateRepeats();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuItem item;
-        item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
-                R.string.add_new_reminder);
-        item.setIcon(R.drawable.ic_menu_reminder);
-        item.setAlphabeticShortcut('r');
-
-        item = menu.add(MENU_GROUP_SHOW_OPTIONS, MENU_SHOW_EXTRA_OPTIONS, 0,
-                R.string.edit_event_show_extra_options);
-        item.setIcon(R.drawable.ic_menu_show_list);
-        item = menu.add(MENU_GROUP_HIDE_OPTIONS, MENU_HIDE_EXTRA_OPTIONS, 0,
-                R.string.edit_event_hide_extra_options);
-        item.setIcon(R.drawable.ic_menu_show_list);
-
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        if (mReminderItems.size() < MAX_REMINDERS) {
-            menu.setGroupVisible(MENU_GROUP_REMINDER, true);
-            menu.setGroupEnabled(MENU_GROUP_REMINDER, true);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_REMINDER, false);
-            menu.setGroupEnabled(MENU_GROUP_REMINDER, false);
-        }
-
-        if (mExtraOptions.getVisibility() == View.VISIBLE) {
-            menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, false);
-            menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, true);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, true);
-            menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, false);
-        }
-
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    private void addReminder() {
-        // TODO: when adding a new reminder, make it different from the
-        // last one in the list (if any).
-        if (mDefaultReminderMinutes == 0) {
-            addReminder(this, this, mReminderItems, mReminderValues,
-                    mReminderLabels, 10 /* minutes */);
-        } else {
-            addReminder(this, this, mReminderItems, mReminderValues,
-                    mReminderLabels, mDefaultReminderMinutes);
-        }
-        updateRemindersVisibility();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-        case MENU_ADD_REMINDER:
-            addReminder();
-            return true;
-        case MENU_SHOW_EXTRA_OPTIONS:
-            mExtraOptions.setVisibility(View.VISIBLE);
-            return true;
-        case MENU_HIDE_EXTRA_OPTIONS:
-            mExtraOptions.setVisibility(View.GONE);
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public void onBackPressed() {
-        // If we are creating a new event, do not create it if the
-        // title, location and description are all empty, in order to
-        // prevent accidental "no subject" event creations.
-        if (mUri != null || !isEmpty()) {
-            if (!save()) {
-                // We cannot exit this activity because the calendars
-                // are still loading.
-                return;
-            }
-        }
-        finish();
-    }
-
-    private void populateWhen() {
-        long startMillis = mStartTime.toMillis(false /* use isDst */);
-        long endMillis = mEndTime.toMillis(false /* use isDst */);
-        setDate(mStartDateButton, startMillis);
-        setDate(mEndDateButton, endMillis);
-
-        setTime(mStartTimeButton, startMillis);
-        setTime(mEndTimeButton, endMillis);
-
-        mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
-        mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
-
-        mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
-        mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
-    }
-
-    private void populateTimezone() {
-        mTimezoneButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                showTimezoneDialog();
-            }
-        });
-        setTimezone(mTimezoneAdapter.getRowById(mTimezone));
-    }
-
-    /**
-     * Checks if the start and end times for this event should be
-     * displayed in the Calendar app's time zone as well and
-     * formats and displays them.
-     */
-    private void updateHomeTime() {
-        String tz = Utils.getTimeZone(this, mUpdateTZ);
-        if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone)) {
-            int flags = DateUtils.FORMAT_SHOW_TIME;
-            boolean is24Format = DateFormat.is24HourFormat(this);
-            if (is24Format) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            long millisStart = mStartTime.toMillis(false);
-            long millisEnd = mEndTime.toMillis(false);
-
-            boolean isDSTStart = mStartTime.isDst != 0;
-            boolean isDSTEnd = mEndTime.isDst != 0;
-
-            // First update the start date and times
-            String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(isDSTStart,
-                    TimeZone.SHORT, Locale.getDefault());
-            StringBuilder time = new StringBuilder();
-
-            mSB.setLength(0);
-            time.append(DateUtils.formatDateRange(this, mF, millisStart, millisStart, flags, tz))
-                    .append(" ").append(tzDisplay);
-            mStartTimeHome.setText(time.toString());
-
-            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
-                    DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
-            mSB.setLength(0);
-            mStartDateHome.setText(DateUtils.formatDateRange(this, mF, millisStart, millisStart,
-                    flags, tz).toString());
-
-            // Make any adjustments needed for the end times
-            if (isDSTEnd != isDSTStart) {
-                tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(isDSTEnd,
-                        TimeZone.SHORT, Locale.getDefault());
-            }
-            flags = DateUtils.FORMAT_SHOW_TIME;
-            if (is24Format) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-
-            // Then update the end times
-            time.setLength(0);
-            mSB.setLength(0);
-            time.append(DateUtils.formatDateRange(this, mF, millisEnd, millisEnd, flags, tz))
-                    .append(" ").append(tzDisplay);
-            mEndTimeHome.setText(time.toString());
-
-            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
-            DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
-            mSB.setLength(0);
-            mEndDateHome.setText(DateUtils.formatDateRange(this, mF, millisEnd, millisEnd,
-                    flags, tz).toString());
-
-            mStartTimeHome.setVisibility(View.VISIBLE);
-            mStartDateHome.setVisibility(View.VISIBLE);
-            mEndTimeHome.setVisibility(View.VISIBLE);
-            mEndDateHome.setVisibility(View.VISIBLE);
-        } else {
-            mStartTimeHome.setVisibility(View.GONE);
-            mStartDateHome.setVisibility(View.GONE);
-            mEndTimeHome.setVisibility(View.GONE);
-            mEndDateHome.setVisibility(View.GONE);
-        }
-    }
-
-    /**
-     * Removes "Show all timezone" footer and adds all timezones to the dialog.
-     */
-    private void showAllTimezone(ListView listView) {
-        final ListView lv = listView;  // For making this variable available from Runnable.
-        lv.removeFooterView(mTimezoneFooterView);
-        mTimezoneAdapter.showAllTimezones();
-        final int row = mTimezoneAdapter.getRowById(mTimezone);
-        // we need to post the selection changes to have them have any effect.
-        lv.post(new Runnable() {
-            @Override
-            public void run() {
-                lv.setItemChecked(row, true);
-                lv.setSelection(row);
-            }
-        });
-    }
-
-    private void showTimezoneDialog() {
-        mTimezoneAdapter = new TimezoneAdapter(this, mTimezone);
-        final int row = mTimezoneAdapter.getRowById(mTimezone);
-        mTimezoneDialog = new AlertDialog.Builder(this)
-                .setTitle(R.string.timezone_label)
-                .setSingleChoiceItems(mTimezoneAdapter, row, this)
-                .create();
-        final ListView lv = mTimezoneDialog.getListView();
-        mTimezoneFooterView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                showAllTimezone(lv);
-            }
-        });
-        lv.addFooterView(mTimezoneFooterView);
-        mTimezoneDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
-            @Override
-            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
-                if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER &&
-                        lv.getSelectedView() == mTimezoneFooterView) {
-                    showAllTimezone(lv);
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-        });
-        mTimezoneDialog.show();
-    }
-
-    private void populateRepeats() {
-        Time time = mStartTime;
-        Resources r = getResources();
-        int resource = android.R.layout.simple_spinner_item;
-
-        String[] days = new String[] {
-            DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
-            DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM),
-        };
-        String[] ordinals = r.getStringArray(R.array.ordinal_labels);
-
-        // Only display "Custom" in the spinner if the device does not support the
-        // recurrence functionality of the event. Only display every weekday if
-        // the event starts on a weekday.
-        boolean isCustomRecurrence = isCustomRecurrence();
-        boolean isWeekdayEvent = isWeekdayEvent();
-
-        ArrayList<String> repeatArray = new ArrayList<String>(0);
-        ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
-
-        repeatArray.add(r.getString(R.string.does_not_repeat));
-        recurrenceIndexes.add(DOES_NOT_REPEAT);
-
-        repeatArray.add(r.getString(R.string.daily));
-        recurrenceIndexes.add(REPEATS_DAILY);
-
-        if (isWeekdayEvent) {
-            repeatArray.add(r.getString(R.string.every_weekday));
-            recurrenceIndexes.add(REPEATS_EVERY_WEEKDAY);
-        }
-
-        String format = r.getString(R.string.weekly);
-        repeatArray.add(String.format(format, time.format("%A")));
-        recurrenceIndexes.add(REPEATS_WEEKLY_ON_DAY);
-
-        // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance of the given day.
-        int dayNumber = (time.monthDay - 1) / 7;
-        format = r.getString(R.string.monthly_on_day_count);
-        repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
-        recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY_COUNT);
-
-        format = r.getString(R.string.monthly_on_day);
-        repeatArray.add(String.format(format, time.monthDay));
-        recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY);
-
-        long when = time.toMillis(false);
-        format = r.getString(R.string.yearly);
-        int flags = 0;
-        if (DateFormat.is24HourFormat(this)) {
-            flags |= DateUtils.FORMAT_24HOUR;
-        }
-        repeatArray.add(String.format(format, DateUtils.formatDateTime(this, when, flags)));
-        recurrenceIndexes.add(REPEATS_YEARLY);
-
-        if (isCustomRecurrence) {
-            repeatArray.add(r.getString(R.string.custom));
-            recurrenceIndexes.add(REPEATS_CUSTOM);
-        }
-        mRecurrenceIndexes = recurrenceIndexes;
-
-        int position = recurrenceIndexes.indexOf(DOES_NOT_REPEAT);
-        if (mRrule != null) {
-            if (isCustomRecurrence) {
-                position = recurrenceIndexes.indexOf(REPEATS_CUSTOM);
-            } else {
-                switch (mEventRecurrence.freq) {
-                    case EventRecurrence.DAILY:
-                        position = recurrenceIndexes.indexOf(REPEATS_DAILY);
-                        break;
-                    case EventRecurrence.WEEKLY:
-                        if (mEventRecurrence.repeatsOnEveryWeekDay()) {
-                            position = recurrenceIndexes.indexOf(REPEATS_EVERY_WEEKDAY);
-                        } else {
-                            position = recurrenceIndexes.indexOf(REPEATS_WEEKLY_ON_DAY);
-                        }
-                        break;
-                    case EventRecurrence.MONTHLY:
-                        if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
-                            position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY_COUNT);
-                        } else {
-                            position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY);
-                        }
-                        break;
-                    case EventRecurrence.YEARLY:
-                        position = recurrenceIndexes.indexOf(REPEATS_YEARLY);
-                        break;
-                }
-            }
-        }
-        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, resource, repeatArray);
-        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mRepeatsSpinner.setAdapter(adapter);
-        mRepeatsSpinner.setSelection(position);
-    }
-
-    // Adds a reminder to the displayed list of reminders.
-    // Returns true if successfully added reminder, false if no reminders can
-    // be added.
-    static boolean addReminder(Activity activity, View.OnClickListener listener,
-            ArrayList<LinearLayout> items, ArrayList<Integer> values,
-            ArrayList<String> labels, int minutes) {
-
-        if (items.size() >= MAX_REMINDERS) {
-            return false;
-        }
-
-        LayoutInflater inflater = activity.getLayoutInflater();
-        LinearLayout parent = (LinearLayout) activity.findViewById(R.id.reminder_items_container);
-        LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, null);
-        parent.addView(reminderItem);
-
-        Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value);
-        Resources res = activity.getResources();
-        spinner.setPrompt(res.getString(R.string.reminders_label));
-        int resource = android.R.layout.simple_spinner_item;
-        ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, resource, labels);
-        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        spinner.setAdapter(adapter);
-
-        ImageButton reminderRemoveButton;
-        reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove);
-        reminderRemoveButton.setOnClickListener(listener);
-
-        int index = findMinutesInReminderList(values, minutes);
-        spinner.setSelection(index);
-        items.add(reminderItem);
-
-        return true;
-    }
-
-    static void addMinutesToList(Context context, ArrayList<Integer> values,
-            ArrayList<String> labels, int minutes) {
-        int index = values.indexOf(minutes);
-        if (index != -1) {
-            return;
-        }
-
-        // The requested "minutes" does not exist in the list, so insert it
-        // into the list.
-
-        String label = constructReminderLabel(context, minutes, false);
-        int len = values.size();
-        for (int i = 0; i < len; i++) {
-            if (minutes < values.get(i)) {
-                values.add(i, minutes);
-                labels.add(i, label);
-                return;
-            }
-        }
-
-        values.add(minutes);
-        labels.add(len, label);
-    }
-
-    /**
-     * Finds the index of the given "minutes" in the "values" list.
-     *
-     * @param values the list of minutes corresponding to the spinner choices
-     * @param minutes the minutes to search for in the values list
-     * @return the index of "minutes" in the "values" list
-     */
-    private static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) {
-        int index = values.indexOf(minutes);
-        if (index == -1) {
-            // This should never happen.
-            Log.e("Cal", "Cannot find minutes (" + minutes + ") in list");
-            return 0;
-        }
-        return index;
-    }
-
-    // Constructs a label given an arbitrary number of minutes.  For example,
-    // if the given minutes is 63, then this returns the string "63 minutes".
-    // As another example, if the given minutes is 120, then this returns
-    // "2 hours".
-    static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
-        Resources resources = context.getResources();
-        int value, resId;
-
-        if (minutes % 60 != 0) {
-            value = minutes;
-            if (abbrev) {
-                resId = R.plurals.Nmins;
-            } else {
-                resId = R.plurals.Nminutes;
-            }
-        } else if (minutes % (24 * 60) != 0) {
-            value = minutes / 60;
-            resId = R.plurals.Nhours;
-        } else {
-            value = minutes / ( 24 * 60);
-            resId = R.plurals.Ndays;
-        }
-
-        String format = resources.getQuantityString(resId, value);
-        return String.format(format, value);
-    }
-
-    private void updateRemindersVisibility() {
-        if (mReminderItems.size() == 0) {
-            mRemindersSeparator.setVisibility(View.GONE);
-            mRemindersContainer.setVisibility(View.GONE);
-        } else {
-            mRemindersSeparator.setVisibility(View.VISIBLE);
-            mRemindersContainer.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void setDate(TextView view, long millis) {
-        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
-                DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH |
-                DateUtils.FORMAT_ABBREV_WEEKDAY;
-
-        mSB.setLength(0);
-        String dateString = DateUtils.formatDateRange(this, mF, millis, millis, flags, mTimezone)
-                .toString();
-        view.setText(dateString);
-    }
-
-    private void setTime(TextView view, long millis) {
-        int flags = DateUtils.FORMAT_SHOW_TIME;
-        if (DateFormat.is24HourFormat(this)) {
-            flags |= DateUtils.FORMAT_24HOUR;
-        }
-        mSB.setLength(0);
-        String timeString = DateUtils.formatDateRange(this, mF, millis, millis, flags, mTimezone)
-                .toString();
-        view.setText(timeString);
-    }
-
-    private void setTimezone(int i) {
-        if (i < 0 || i > mTimezoneAdapter.getCount()) {
-            return; // do nothing
-        }
-        TimezoneRow timezone = mTimezoneAdapter.getItem(i);
-        mTimezoneButton.setText(timezone.toString());
-        mTimezone = timezone.mId;
-        mTimezoneAdapter.setCurrentTimezone(mTimezone);
-        mStartTime.timezone = mTimezone;
-        mStartTime.normalize(true);
-        mEndTime.timezone = mTimezone;
-        mEndTime.normalize(true);
-    }
-
-    // Saves the event.  Returns true if it is okay to exit this activity.
-    private boolean save() {
-        boolean forceSaveReminders = false;
-
-        // If we are creating a new event, then make sure we wait until the
-        // query to fetch the list of calendars has finished.
-        if (mEventCursor == null) {
-            if (!mCalendarsQueryComplete) {
-                // Wait for the calendars query to finish.
-                if (mLoadingCalendarsDialog == null) {
-                    // Create the progress dialog
-                    mLoadingCalendarsDialog = ProgressDialog.show(this,
-                            getText(R.string.loading_calendars_title),
-                            getText(R.string.loading_calendars_message),
-                            true, true, this);
-                    mSaveAfterQueryComplete = true;
-                }
-                return false;
-            }
-
-            // Avoid creating a new event if the calendars cursor is empty or we clicked through
-            // too quickly and no calendar was selected (blame the monkey)
-            if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0 ||
-                    mCalendarsSpinner.getSelectedItemId() == AdapterView.INVALID_ROW_ID) {
-                Log.w("Cal", "The calendars table does not contain any calendars"
-                        + " or no calendar was selected."
-                        + " New event was not created.");
-                return true;
-            }
-            Toast.makeText(this, R.string.creating_event, Toast.LENGTH_SHORT).show();
-        } else {
-            Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
-        }
-
-        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
-        int eventIdIndex = -1;
-
-        ContentValues values = getContentValuesFromUi();
-        Uri uri = mUri;
-
-        // save the timezone as a recent one
-        if (!mAllDayCheckBox.isChecked()) {
-            mTimezoneAdapter.saveRecentTimezone(mTimezone);
-        }
-
-        // Update the "hasAlarm" field for the event
-        ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
-                mReminderValues);
-        int len = reminderMinutes.size();
-        values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
-
-        // For recurring events, we must make sure that we use duration rather
-        // than dtend.
-        if (uri == null) {
-            // Add hasAttendeeData for a new event
-            values.put(Events.HAS_ATTENDEE_DATA, 1);
-            // Create new event with new contents
-            addRecurrenceRule(values);
-            if (mRrule != null) {
-                values.remove(Events.DTEND);
-            }
-            eventIdIndex = ops.size();
-            Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
-            ops.add(b.build());
-            forceSaveReminders = true;
-
-        } else if (mRrule == null) {
-            // Modify contents of a non-repeating event
-            addRecurrenceRule(values);
-            checkTimeDependentFields(values);
-            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
-
-        } else if (mInitialValues.getAsString(Events.RRULE) == null) {
-            // This event was changed from a non-repeating event to a
-            // repeating event.
-            addRecurrenceRule(values);
-            values.remove(Events.DTEND);
-            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
-
-        } else if (mModification == MODIFY_SELECTED) {
-            // Modify contents of the current instance of repeating event
-
-            // Create a recurrence exception
-            long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
-            values.put(Events.ORIGINAL_EVENT, mEventCursor.getString(EVENT_INDEX_SYNC_ID));
-            values.put(Events.ORIGINAL_INSTANCE_TIME, begin);
-            boolean allDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
-            values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
-
-            eventIdIndex = ops.size();
-            Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
-            ops.add(b.build());
-            forceSaveReminders = true;
-
-        } else if (mModification == MODIFY_ALL_FOLLOWING) {
-            // Modify this instance and all future instances of repeating event
-            addRecurrenceRule(values);
-
-            if (mRrule == null) {
-                // We've changed a recurring event to a non-recurring event.
-                // If the event we are editing is the first in the series,
-                // then delete the whole series.  Otherwise, update the series
-                // to end at the new start time.
-                if (isFirstEventInSeries()) {
-                    ops.add(ContentProviderOperation.newDelete(uri).build());
-                } else {
-                    // Update the current repeating event to end at the new
-                    // start time.
-                    updatePastEvents(ops, uri);
-                }
-                eventIdIndex = ops.size();
-                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
-                        .build());
-            } else {
-                if (isFirstEventInSeries()) {
-                    checkTimeDependentFields(values);
-                    values.remove(Events.DTEND);
-                    Builder b = ContentProviderOperation.newUpdate(uri).withValues(values);
-                    ops.add(b.build());
-                } else {
-                    // Update the current repeating event to end at the new
-                    // start time.
-                    updatePastEvents(ops, uri);
-
-                    // Create a new event with the user-modified fields
-                    values.remove(Events.DTEND);
-                    eventIdIndex = ops.size();
-                    ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(
-                            values).build());
-                }
-            }
-            forceSaveReminders = true;
-
-        } else if (mModification == MODIFY_ALL) {
-
-            // Modify all instances of repeating event
-            addRecurrenceRule(values);
-
-            if (mRrule == null) {
-                // We've changed a recurring event to a non-recurring event.
-                // Delete the whole series and replace it with a new
-                // non-recurring event.
-                ops.add(ContentProviderOperation.newDelete(uri).build());
-
-                eventIdIndex = ops.size();
-                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
-                        .build());
-                forceSaveReminders = true;
-            } else {
-                checkTimeDependentFields(values);
-                values.remove(Events.DTEND);
-                ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
-            }
-        }
-
-        // New Event or New Exception to an existing event
-        boolean newEvent = (eventIdIndex != -1);
-
-        if (newEvent) {
-            saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, mOriginalMinutes,
-                    forceSaveReminders);
-        } else if (uri != null) {
-            long eventId = ContentUris.parseId(uri);
-            saveReminders(ops, eventId, reminderMinutes, mOriginalMinutes,
-                    forceSaveReminders);
-        }
-
-        Builder b;
-
-        // New event/instance - Set Organizer's response as yes
-        if (mHasAttendeeData && newEvent) {
-            values.clear();
-            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
-
-            // Save the default calendar for new events
-            if (mCalendarsCursor != null) {
-                if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
-                    String defaultCalendar = mCalendarsCursor
-                            .getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-                    Utils.setSharedPreference(this,
-                            CalendarPreferenceActivity.KEY_DEFAULT_CALENDAR, defaultCalendar);
-                }
-            }
-
-            String ownerEmail = mOwnerAccount;
-            // Just in case mOwnerAccount is null, try to get owner from mCalendarsCursor
-            if (ownerEmail == null && mCalendarsCursor != null &&
-                    mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
-                ownerEmail = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-            }
-            if (ownerEmail != null) {
-                values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
-                values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
-                values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
-                int initialStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
-
-                // Don't accept for secondary calendars
-                if (ownerEmail.endsWith("calendar.google.com")) {
-                    initialStatus = Attendees.ATTENDEE_STATUS_NONE;
-                }
-                values.put(Attendees.ATTENDEE_STATUS, initialStatus);
-
-                b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
-                        .withValues(values);
-                b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
-                ops.add(b.build());
-            }
-        }
-
-        // TODO: is this the right test?  this currently checks if this is
-        // a new event or an existing event.  or is this a paranoia check?
-        if (mHasAttendeeData && (newEvent || uri != null)) {
-            Editable attendeesText = mAttendeesList.getText();
-            // Hit the content provider only if this is a new event or the user has changed it
-            if (newEvent || !mOriginalAttendees.equals(attendeesText.toString())) {
-                // figure out which attendees need to be added and which ones
-                // need to be deleted.  use a linked hash set, so we maintain
-                // order (but also remove duplicates).
-                LinkedHashSet<Rfc822Token> newAttendees = getAddressesFromList(mAttendeesList);
-
-                // the eventId is only used if eventIdIndex is -1.
-                // TODO: clean up this code.
-                long eventId = uri != null ? ContentUris.parseId(uri) : -1;
-
-                // only compute deltas if this is an existing event.
-                // new events (being inserted into the Events table) won't
-                // have any existing attendees.
-                if (!newEvent) {
-                    HashSet<Rfc822Token> removedAttendees = new HashSet<Rfc822Token>();
-                    HashSet<Rfc822Token> originalAttendees = new HashSet<Rfc822Token>();
-                    Rfc822Tokenizer.tokenize(mOriginalAttendees, originalAttendees);
-                    for (Rfc822Token originalAttendee : originalAttendees) {
-                        if (newAttendees.contains(originalAttendee)) {
-                            // existing attendee.  remove from new attendees set.
-                            newAttendees.remove(originalAttendee);
-                        } else {
-                            // no longer in attendees.  mark as removed.
-                            removedAttendees.add(originalAttendee);
-                        }
-                    }
-
-                    // delete removed attendees
-                    b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
-
-                    String[] args = new String[removedAttendees.size() + 1];
-                    args[0] = Long.toString(eventId);
-                    int i = 1;
-                    StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX);
-                    for (Rfc822Token removedAttendee : removedAttendees) {
-                        if (i > 1) {
-                            deleteWhere.append(",");
-                        }
-                        deleteWhere.append("?");
-                        args[i++] = removedAttendee.getAddress();
-                    }
-                    deleteWhere.append(")");
-                    b.withSelection(deleteWhere.toString(), args);
-                    ops.add(b.build());
-                }
-
-                if (newAttendees.size() > 0) {
-                    // Insert the new attendees
-                    for (Rfc822Token attendee : newAttendees) {
-                        values.clear();
-                        values.put(Attendees.ATTENDEE_NAME, attendee.getName());
-                        values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress());
-                        values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
-                        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
-                        values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
-
-                        if (newEvent) {
-                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
-                                    .withValues(values);
-                            b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
-                        } else {
-                            values.put(Attendees.EVENT_ID, eventId);
-                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
-                                    .withValues(values);
-                        }
-                        ops.add(b.build());
-                    }
-                }
-            }
-        }
-
-        try {
-            // TODO Move this to background thread
-            ContentProviderResult[] results =
-                getContentResolver().applyBatch(android.provider.Calendar.AUTHORITY, ops);
-            if (DEBUG) {
-                for (int i = 0; i < results.length; i++) {
-                    Log.v(TAG, "results = " + results[i].toString());
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Ignoring unexpected remote exception", e);
-        } catch (OperationApplicationException e) {
-            Log.w(TAG, "Ignoring unexpected exception", e);
-        }
-
-        return true;
-    }
-
-    private boolean isFirstEventInSeries() {
-        int dtStart = mEventCursor.getColumnIndexOrThrow(Events.DTSTART);
-        long start = mEventCursor.getLong(dtStart);
-        return start == mStartTime.toMillis(true);
-    }
-
-    private void updatePastEvents(ArrayList<ContentProviderOperation> ops, Uri uri) {
-        long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
-        String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION);
-        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
-        String oldRrule = mEventCursor.getString(EVENT_INDEX_RRULE);
-        mEventRecurrence.parse(oldRrule);
-
-        Time untilTime = new Time();
-        long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
-        ContentValues oldValues = new ContentValues();
-
-        // The "until" time must be in UTC time in order for Google calendar
-        // to display it properly.  For all-day events, the "until" time string
-        // must include just the date field, and not the time field.  The
-        // repeating events repeat up to and including the "until" time.
-        untilTime.timezone = Time.TIMEZONE_UTC;
-
-        // Subtract one second from the old begin time to get the new
-        // "until" time.
-        untilTime.set(begin - 1000);  // subtract one second (1000 millis)
-        if (allDay) {
-            untilTime.hour = 0;
-            untilTime.minute = 0;
-            untilTime.second = 0;
-            untilTime.allDay = true;
-            untilTime.normalize(false);
-
-            // For all-day events, the duration must be in days, not seconds.
-            // Otherwise, Google Calendar will (mistakenly) change this event
-            // into a non-all-day event.
-            int len = oldDuration.length();
-            if (oldDuration.charAt(0) == 'P' && oldDuration.charAt(len - 1) == 'S') {
-                int seconds = Integer.parseInt(oldDuration.substring(1, len - 1));
-                int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
-                oldDuration = "P" + days + "D";
-            }
-        }
-        mEventRecurrence.until = untilTime.format2445();
-
-        oldValues.put(Events.DTSTART, oldStartMillis);
-        oldValues.put(Events.DURATION, oldDuration);
-        oldValues.put(Events.RRULE, mEventRecurrence.toString());
-        Builder b = ContentProviderOperation.newUpdate(uri).withValues(oldValues);
-        ops.add(b.build());
-    }
-
-    private void checkTimeDependentFields(ContentValues values) {
-        long oldBegin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
-        long oldEnd = mInitialValues.getAsLong(EVENT_END_TIME);
-        boolean oldAllDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
-        String oldRrule = mInitialValues.getAsString(Events.RRULE);
-        String oldTimezone = mInitialValues.getAsString(Events.EVENT_TIMEZONE);
-
-        long newBegin = values.getAsLong(Events.DTSTART);
-        long newEnd = values.getAsLong(Events.DTEND);
-        boolean newAllDay = values.getAsInteger(Events.ALL_DAY) != 0;
-        String newRrule = values.getAsString(Events.RRULE);
-        String newTimezone = values.getAsString(Events.EVENT_TIMEZONE);
-
-        // If none of the time-dependent fields changed, then remove them.
-        if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
-                && TextUtils.equals(oldRrule, newRrule)
-                && TextUtils.equals(oldTimezone, newTimezone)) {
-            values.remove(Events.DTSTART);
-            values.remove(Events.DTEND);
-            values.remove(Events.DURATION);
-            values.remove(Events.ALL_DAY);
-            values.remove(Events.RRULE);
-            values.remove(Events.EVENT_TIMEZONE);
-            return;
-        }
-
-        if (oldRrule == null || newRrule == null) {
-            return;
-        }
-
-        // If we are modifying all events then we need to set DTSTART to the
-        // start time of the first event in the series, not the current
-        // date and time.  If the start time of the event was changed
-        // (from, say, 3pm to 4pm), then we want to add the time difference
-        // to the start time of the first event in the series (the DTSTART
-        // value).  If we are modifying one instance or all following instances,
-        // then we leave the DTSTART field alone.
-        if (mModification == MODIFY_ALL) {
-            long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
-            if (oldBegin != newBegin) {
-                // The user changed the start time of this event
-                long offset = newBegin - oldBegin;
-                oldStartMillis += offset;
-            }
-            values.put(Events.DTSTART, oldStartMillis);
-        }
-    }
-
-    static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
-            ArrayList<Integer> reminderValues) {
-        int len = reminderItems.size();
-        ArrayList<Integer> reminderMinutes = new ArrayList<Integer>(len);
-        for (int index = 0; index < len; index++) {
-            LinearLayout layout = reminderItems.get(index);
-            Spinner spinner = (Spinner) layout.findViewById(R.id.reminder_value);
-            int minutes = reminderValues.get(spinner.getSelectedItemPosition());
-            reminderMinutes.add(minutes);
-        }
-        return reminderMinutes;
-    }
-
-    /**
-     * Saves the reminders, if they changed.  Returns true if the database
-     * was updated.
-     *
-     * @param ops the array of ContentProviderOperations
-     * @param eventId the id of the event whose reminders are being updated
-     * @param reminderMinutes the array of reminders set by the user
-     * @param originalMinutes the original array of reminders
-     * @param forceSave if true, then save the reminders even if they didn't
-     *   change
-     * @return true if the database was updated
-     */
-    static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId,
-            ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
-            boolean forceSave) {
-        // If the reminders have not changed, then don't update the database
-        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
-            return false;
-        }
-
-        // Delete all the existing reminders for this event
-        String where = Reminders.EVENT_ID + "=?";
-        String[] args = new String[] { Long.toString(eventId) };
-        Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
-        b.withSelection(where, args);
-        ops.add(b.build());
-
-        ContentValues values = new ContentValues();
-        int len = reminderMinutes.size();
-
-        // Insert the new reminders, if any
-        for (int i = 0; i < len; i++) {
-            int minutes = reminderMinutes.get(i);
-
-            values.clear();
-            values.put(Reminders.MINUTES, minutes);
-            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
-            values.put(Reminders.EVENT_ID, eventId);
-            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
-            ops.add(b.build());
-        }
-        return true;
-    }
-
-    static boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
-            int eventIdIndex, ArrayList<Integer> reminderMinutes,
-            ArrayList<Integer> originalMinutes, boolean forceSave) {
-        // If the reminders have not changed, then don't update the database
-        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
-            return false;
-        }
-
-        // Delete all the existing reminders for this event
-        Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
-        b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
-        b.withSelectionBackReference(0, eventIdIndex);
-        ops.add(b.build());
-
-        ContentValues values = new ContentValues();
-        int len = reminderMinutes.size();
-
-        // Insert the new reminders, if any
-        for (int i = 0; i < len; i++) {
-            int minutes = reminderMinutes.get(i);
-
-            values.clear();
-            values.put(Reminders.MINUTES, minutes);
-            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
-            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
-            b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
-            ops.add(b.build());
-        }
-        return true;
-    }
-
-    private void addRecurrenceRule(ContentValues values) {
-        updateRecurrenceRule();
-
-        if (mRrule == null) {
-            return;
-        }
-
-        values.put(Events.RRULE, mRrule);
-        long end = mEndTime.toMillis(true /* ignore dst */);
-        long start = mStartTime.toMillis(true /* ignore dst */);
-        String duration;
-
-        boolean isAllDay = mAllDayCheckBox.isChecked();
-        if (isAllDay) {
-            long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) / DateUtils.DAY_IN_MILLIS;
-            duration = "P" + days + "D";
-        } else {
-            long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
-            duration = "P" + seconds + "S";
-        }
-        values.put(Events.DURATION, duration);
-    }
-
-    private void clearRecurrence() {
-        mEventRecurrence.byday = null;
-        mEventRecurrence.bydayNum = null;
-        mEventRecurrence.bydayCount = 0;
-        mEventRecurrence.bymonth = null;
-        mEventRecurrence.bymonthCount = 0;
-        mEventRecurrence.bymonthday = null;
-        mEventRecurrence.bymonthdayCount = 0;
-    }
-
-    private void updateRecurrenceRule() {
-        int position = mRepeatsSpinner.getSelectedItemPosition();
-        int selection = mRecurrenceIndexes.get(position);
-        // Make sure we don't have any leftover data from the previous setting
-        clearRecurrence();
-
-        if (selection == DOES_NOT_REPEAT) {
-            mRrule = null;
-            return;
-        } else if (selection == REPEATS_CUSTOM) {
-            // Keep custom recurrence as before.
-            return;
-        } else if (selection == REPEATS_DAILY) {
-            mEventRecurrence.freq = EventRecurrence.DAILY;
-        } else if (selection == REPEATS_EVERY_WEEKDAY) {
-            mEventRecurrence.freq = EventRecurrence.WEEKLY;
-            int dayCount = 5;
-            int[] byday = new int[dayCount];
-            int[] bydayNum = new int[dayCount];
-
-            byday[0] = EventRecurrence.MO;
-            byday[1] = EventRecurrence.TU;
-            byday[2] = EventRecurrence.WE;
-            byday[3] = EventRecurrence.TH;
-            byday[4] = EventRecurrence.FR;
-            for (int day = 0; day < dayCount; day++) {
-                bydayNum[day] = 0;
-            }
-
-            mEventRecurrence.byday = byday;
-            mEventRecurrence.bydayNum = bydayNum;
-            mEventRecurrence.bydayCount = dayCount;
-        } else if (selection == REPEATS_WEEKLY_ON_DAY) {
-            mEventRecurrence.freq = EventRecurrence.WEEKLY;
-            int[] days = new int[1];
-            int dayCount = 1;
-            int[] dayNum = new int[dayCount];
-
-            days[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
-            // not sure why this needs to be zero, but set it for now.
-            dayNum[0] = 0;
-
-            mEventRecurrence.byday = days;
-            mEventRecurrence.bydayNum = dayNum;
-            mEventRecurrence.bydayCount = dayCount;
-        } else if (selection == REPEATS_MONTHLY_ON_DAY) {
-            mEventRecurrence.freq = EventRecurrence.MONTHLY;
-            mEventRecurrence.bydayCount = 0;
-            mEventRecurrence.bymonthdayCount = 1;
-            int[] bymonthday = new int[1];
-            bymonthday[0] = mStartTime.monthDay;
-            mEventRecurrence.bymonthday = bymonthday;
-        } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) {
-            mEventRecurrence.freq = EventRecurrence.MONTHLY;
-            mEventRecurrence.bydayCount = 1;
-            mEventRecurrence.bymonthdayCount = 0;
-
-            int[] byday = new int[1];
-            int[] bydayNum = new int[1];
-            // Compute the week number (for example, the "2nd" Monday)
-            int dayCount = 1 + ((mStartTime.monthDay - 1) / 7);
-            if (dayCount == 5) {
-                dayCount = -1;
-            }
-            bydayNum[0] = dayCount;
-            byday[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
-            mEventRecurrence.byday = byday;
-            mEventRecurrence.bydayNum = bydayNum;
-        } else if (selection == REPEATS_YEARLY) {
-            mEventRecurrence.freq = EventRecurrence.YEARLY;
-        }
-
-        // Set the week start day.
-        mEventRecurrence.wkst = EventRecurrence.calendarDay2Day(mFirstDayOfWeek);
-        mRrule = mEventRecurrence.toString();
-    }
-
-    private ContentValues getContentValuesFromUi() {
-        String title = mTitleTextView.getText().toString().trim();
-        boolean isAllDay = mAllDayCheckBox.isChecked();
-        String location = mLocationTextView.getText().toString().trim();
-        String description = mDescriptionTextView.getText().toString().trim();
-
-        ContentValues values = new ContentValues();
-
-        long startMillis;
-        long endMillis;
-        long calendarId;
-        if (isAllDay) {
-            // Reset start and end time, increment the monthDay by 1, and set
-            // the timezone to UTC, as required for all-day events.
-            mTimezone = Time.TIMEZONE_UTC;
-            mStartTime.hour = 0;
-            mStartTime.minute = 0;
-            mStartTime.second = 0;
-            mStartTime.timezone = mTimezone;
-            startMillis = mStartTime.normalize(true);
-
-            mEndTime.hour = 0;
-            mEndTime.minute = 0;
-            mEndTime.second = 0;
-            mEndTime.monthDay++;
-            mEndTime.timezone = mTimezone;
-            endMillis = mEndTime.normalize(true);
-
-            if (mEventCursor == null) {
-                // This is a new event
-                calendarId = mCalendarsSpinner.getSelectedItemId();
-            } else {
-                calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
-            }
-        } else {
-            if (mEventCursor != null) {
-                calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
-            } else {
-                // This is a new event
-                calendarId = mCalendarsSpinner.getSelectedItemId();
-            }
-            // mTimezone is set automatically in onClick
-            mStartTime.timezone = mTimezone;
-            mEndTime.timezone = mTimezone;
-            startMillis = mStartTime.toMillis(true);
-            endMillis = mEndTime.toMillis(true);
-        }
-
-        values.put(Events.CALENDAR_ID, calendarId);
-        values.put(Events.EVENT_TIMEZONE, mTimezone);
-        values.put(Events.TITLE, title);
-        values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
-        values.put(Events.DTSTART, startMillis);
-        values.put(Events.DTEND, endMillis);
-        values.put(Events.DESCRIPTION, description);
-        values.put(Events.EVENT_LOCATION, location);
-        values.put(Events.TRANSPARENCY, mAvailabilitySpinner.getSelectedItemPosition());
-
-        int visibility = mVisibilitySpinner.getSelectedItemPosition();
-        if (visibility > 0) {
-            // For now we the array contains the values 0, 2, and 3. We add one to match.
-            visibility++;
-        }
-        values.put(Events.VISIBILITY, visibility);
-
-        return values;
-    }
-
-    private boolean isEmpty() {
-        String title = mTitleTextView.getText().toString().trim();
-        if (title.length() > 0) {
-            return false;
-        }
-
-        String location = mLocationTextView.getText().toString().trim();
-        if (location.length() > 0) {
-            return false;
-        }
-
-        String description = mDescriptionTextView.getText().toString().trim();
-        if (description.length() > 0) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean isCustomRecurrence() {
-
-        if (mEventRecurrence.until != null || mEventRecurrence.interval != 0) {
-            return true;
-        }
-
-        if (mEventRecurrence.freq == 0) {
-            return false;
-        }
-
-        switch (mEventRecurrence.freq) {
-        case EventRecurrence.DAILY:
-            return false;
-        case EventRecurrence.WEEKLY:
-            if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
-                return false;
-            } else if (mEventRecurrence.bydayCount == 1) {
-                return false;
-            }
-            break;
-        case EventRecurrence.MONTHLY:
-            if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
-                return false;
-            } else if (mEventRecurrence.bydayCount == 0 && mEventRecurrence.bymonthdayCount == 1) {
-                return false;
-            }
-            break;
-        case EventRecurrence.YEARLY:
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean isWeekdayEvent() {
-        if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/src/com/android/calendar/EditEventActivity.java b/src/com/android/calendar/EditEventActivity.java
new file mode 100644
index 0000000..ca2b10e
--- /dev/null
+++ b/src/com/android/calendar/EditEventActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import com.android.calendar.event.EditEventFragment;
+
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+public class EditEventActivity extends AbstractCalendarActivity {
+    private static final String TAG = "EditEventActivity";
+
+    private static final boolean DEBUG = false;
+
+    private FrameLayout mView;
+    private FrameLayout mF1;
+    private FrameLayout mF2;
+    private EditEventFragment mFragment1;
+    private EditEventFragment mFragment2;
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.edit_event_fragment);
+        mView = (FrameLayout) findViewById(R.id.edit_event);
+        mF1 = (FrameLayout) findViewById(R.id.container_1);
+        mF2 = new FrameLayout(this);
+
+//        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(300, 380, Gravity.CENTER);
+//        mF2.setPadding(38, 48, 0, 0);
+//        mF2.setForegroundGravity(Gravity.CENTER);
+//        mF2.setId(777);
+//        mView.addView(mF2, params);
+
+        mFragment1 = new EditEventFragment();
+//        mFragment2 = new EditEventFragment(true);
+
+        FragmentTransaction ft = getFragmentManager().openTransaction();
+        ft.add(R.id.container_1, mFragment1);
+        ft.show(mFragment1);
+//        ft.add(777, mFragment2);
+//        ft.show(mFragment2);
+        ft.commit();
+
+    }
+}
diff --git a/src/com/android/calendar/EmailAddressAdapter.java b/src/com/android/calendar/EmailAddressAdapter.java
index bfcb986..be3e223 100644
--- a/src/com/android/calendar/EmailAddressAdapter.java
+++ b/src/com/android/calendar/EmailAddressAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -16,61 +16,53 @@
 
 package com.android.calendar;
 
-import android.content.ContentResolver;
+import com.android.common.contacts.BaseEmailAddressAdapter;
+
 import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.text.util.Rfc822Token;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.ResourceCursorAdapter;
+import android.view.ViewGroup;
 import android.widget.TextView;
 
-// Customized from com.android.email.EmailAddressAdapter
+/**
+* An adaptation of {@link BaseEmailAddressAdapter} for the Email app. The main
+* purpose of the class is to bind the generic implementation to the resources
+* defined locally: strings and layouts.
+*/
+public class EmailAddressAdapter extends BaseEmailAddressAdapter {
 
-public class EmailAddressAdapter extends ResourceCursorAdapter {
-    public static final int NAME_INDEX = 1;
-    public static final int DATA_INDEX = 2;
+   private LayoutInflater mInflater;
 
-    private static final String SORT_ORDER =
-            Contacts.TIMES_CONTACTED + " DESC, " + Contacts.DISPLAY_NAME;
+   public EmailAddressAdapter(Context context) {
+       super(context);
+       mInflater = LayoutInflater.from(context);
+   }
 
-    private ContentResolver mContentResolver;
+   @Override
+   protected View inflateItemView(ViewGroup parent) {
+       return mInflater.inflate(R.layout.email_autocomplete_item, parent, false);
+   }
 
-    private static final String[] PROJECTION = {
-        Data._ID,               // 0
-        Contacts.DISPLAY_NAME,  // 1
-        Email.DATA              // 2
-    };
+   @Override
+   protected View inflateItemViewLoading(ViewGroup parent) {
+       return mInflater.inflate(R.layout.email_autocomplete_item_loading, parent, false);
+   }
 
-    public EmailAddressAdapter(Context context) {
-        super(context, android.R.layout.simple_dropdown_item_1line, null);
-        mContentResolver = context.getContentResolver();
-    }
+   @Override
+   protected void bindView(View view, String directoryType, String directoryName,
+           String displayName, String emailAddress) {
+     TextView text1 = (TextView)view.findViewById(R.id.text1);
+     TextView text2 = (TextView)view.findViewById(R.id.text2);
+     text1.setText(displayName);
+     text2.setText(emailAddress);
+   }
 
-    @Override
-    public final String convertToString(Cursor cursor) {
-        return makeDisplayString(cursor);
-    }
-
-    private final String makeDisplayString(Cursor cursor) {
-        String name = cursor.getString(NAME_INDEX);
-        String address = cursor.getString(DATA_INDEX);
-
-        return new Rfc822Token(name, address, null).toString();
-    }
-
-    @Override
-    public final void bindView(View view, Context context, Cursor cursor) {
-        ((TextView) view).setText(makeDisplayString(cursor));
-    }
-
-    @Override
-    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-        String filter = constraint == null ? "" : constraint.toString();
-        Uri uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(filter));
-        return mContentResolver.query(uri, PROJECTION, null, null, SORT_ORDER);
-    }
+   @Override
+   protected void bindViewLoading(View view, String directoryType, String directoryName) {
+       TextView text1 = (TextView)view.findViewById(R.id.text1);
+       String text = getContext().getString(R.string.directory_searching_fmt,
+               TextUtils.isEmpty(directoryName) ? directoryType : directoryName);
+       text1.setText(text);
+   }
 }
diff --git a/src/com/android/calendar/Event.java b/src/com/android/calendar/Event.java
index 87cc705..746d5d5 100644
--- a/src/com/android/calendar/Event.java
+++ b/src/com/android/calendar/Event.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.os.Debug;
-import android.preference.PreferenceManager;
 import android.provider.Calendar.Attendees;
 import android.provider.Calendar.Events;
 import android.provider.Calendar.Instances;
@@ -294,8 +293,8 @@
             String orderBy = Instances.SORT_CALENDAR_VIEW;
 
             // Respect the preference to show/hide declined events
-            SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context);
-            boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED,
+            SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+            boolean hideDeclined = prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED,
                     false);
 
             String where = null;
@@ -377,8 +376,6 @@
 
                 events.add(e);
             }
-
-            computePositions(events);
         } finally {
             if (c != null) {
                 c.close();
@@ -401,20 +398,28 @@
      * the same time.
      *
      * @param eventsList the list of events, sorted into increasing time order
+     * @param minimumDurationMillis minimum duration acceptable as cell height of each event
+     * rectangle in millisecond. Should be 0 when it is not determined.
      */
-    private static void computePositions(ArrayList<Event> eventsList) {
-        if (eventsList == null)
+    /* package */ static void computePositions(ArrayList<Event> eventsList,
+            float minimumDurationMillis) {
+        if (eventsList == null) {
             return;
+        }
 
         // Compute the column positions separately for the all-day events
-        doComputePositions(eventsList, false);
-        doComputePositions(eventsList, true);
+        doComputePositions(eventsList, minimumDurationMillis, false);
+        doComputePositions(eventsList, minimumDurationMillis, true);
     }
 
     private static void doComputePositions(ArrayList<Event> eventsList,
-            boolean doAllDayEvents) {
-        ArrayList<Event> activeList = new ArrayList<Event>();
-        ArrayList<Event> groupList = new ArrayList<Event>();
+            float minimumDurationMillis, boolean doAllDayEvents) {
+        final ArrayList<Event> activeList = new ArrayList<Event>();
+        final ArrayList<Event> groupList = new ArrayList<Event>();
+
+        if (minimumDurationMillis < 0.0f) {
+            minimumDurationMillis = 0.0f;
+        }
 
         long colMask = 0;
         int maxCols = 0;
@@ -424,31 +429,16 @@
                 continue;
 
             long start = event.getStartMillis();
-            if (false && event.allDay) {
-                Event e = event;
-                Log.i("Cal", "event start,end day: " + e.startDay + "," + e.endDay
-                        + " start,end time: " + e.startTime + "," + e.endTime
-                        + " start,end millis: " + e.getStartMillis() + "," + e.getEndMillis()
-                        + " "  + e.title);
-            }
-
-            // Remove the inactive events.
-            // An event on the active list becomes inactive when its end time + margin time is less
-            // than or equal to the current event's start time. For more information about
-            // the margin time, see the comment in EVENT_OVERWRAP_MARGIN_TIME.
+            // Remove the inactive events. An event on the active list
+            // becomes inactive when its end time is less than or equal to
+            // the current event's start time.
             Iterator<Event> iter = activeList.iterator();
             while (iter.hasNext()) {
-                Event active = iter.next();
+                final Event active = iter.next();
+
                 float duration = Math.max(active.getEndMillis() - active.getStartMillis(),
-                        CalendarView.EVENT_OVERWRAP_MARGIN_TIME);
+                        minimumDurationMillis);
                 if ((active.getStartMillis() + duration) <= start) {
-                    if (false && event.allDay) {
-                        Event e = active;
-                        Log.i("Cal", "  removing: start,end day: " + e.startDay + "," + e.endDay
-                                + " start,end time: " + e.startTime + "," + e.endTime
-                                + " start,end millis: " + e.getStartMillis() + "," + e.getEndMillis()
-                                + " "  + e.title);
-                    }
                     colMask &= ~(1L << active.getColumn());
                     iter.remove();
                 }
diff --git a/src/com/android/calendar/EventGeometry.java b/src/com/android/calendar/EventGeometry.java
index f4c1db2..d5cb245 100644
--- a/src/com/android/calendar/EventGeometry.java
+++ b/src/com/android/calendar/EventGeometry.java
@@ -31,21 +31,21 @@
         mCellMargin = cellMargin;
     }
 
-    void setHourGap(float gap) {
+    public void setHourGap(float gap) {
         mHourGap = gap;
     }
 
-    void setMinEventHeight(float height) {
+    public void setMinEventHeight(float height) {
         mMinEventHeight = height;
     }
 
-    void setHourHeight(float height) {
+    public void setHourHeight(float height) {
         mMinuteHeight = height / 60.0f;
     }
 
     // Computes the rectangle coordinates of the given event on the screen.
     // Returns true if the rectangle is visible on the screen.
-    boolean computeEventRect(int date, int left, int top, int cellWidth, Event event) {
+    public boolean computeEventRect(int date, int left, int top, int cellWidth, Event event) {
         if (event.allDay) {
             return false;
         }
@@ -70,7 +70,7 @@
         // If the event ends on a future day, then show it extending to
         // the end of this day.
         if (endDay > date) {
-            endTime = CalendarView.MINUTES_PER_DAY;
+            endTime = DayView.MINUTES_PER_DAY;
         }
 
         int col = event.getColumn();
diff --git a/src/com/android/calendar/EventInfoActivity.java b/src/com/android/calendar/EventInfoActivity.java
index abfb28e..49da4cd 100644
--- a/src/com/android/calendar/EventInfoActivity.java
+++ b/src/com/android/calendar/EventInfoActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -16,1183 +16,98 @@
 
 package com.android.calendar;
 
+
 import static android.provider.Calendar.EVENT_BEGIN_TIME;
 import static android.provider.Calendar.EVENT_END_TIME;
 import static android.provider.Calendar.AttendeesColumns.ATTENDEE_STATUS;
 
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.AsyncQueryHandler;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+
+import android.app.Fragment;
 import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.pim.EventRecurrence;
-import android.provider.Calendar;
-import android.provider.Calendar.Attendees;
-import android.provider.Calendar.Calendars;
-import android.provider.Calendar.Events;
-import android.provider.Calendar.Reminders;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.Presence;
-import android.provider.ContactsContract.QuickContact;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
 import android.text.format.Time;
-import android.text.util.Linkify;
-import android.text.util.Rfc822Token;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.QuickContactBadge;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.TimeZone;
-import java.util.regex.Pattern;
+public class EventInfoActivity extends AbstractCalendarActivity
+        implements CalendarController.EventHandler {
 
-public class EventInfoActivity extends Activity implements View.OnClickListener,
-        AdapterView.OnItemSelectedListener {
-    public static final boolean DEBUG = false;
+    private static final int HANDLER_KEY = 0;
 
-    public static final String TAG = "EventInfoActivity";
-
-    private static final int MAX_REMINDERS = 5;
-
-    /**
-     * These are the corresponding indices into the array of strings
-     * "R.array.change_response_labels" in the resource file.
-     */
-    static final int UPDATE_SINGLE = 0;
-    static final int UPDATE_ALL = 1;
-
-    private static final String[] EVENT_PROJECTION = new String[] {
-        Events._ID,                  // 0  do not remove; used in DeleteEventHelper
-        Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
-        Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
-        Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
-        Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
-        Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
-        Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
-        Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
-        Events.DESCRIPTION,          // 8
-        Events.EVENT_LOCATION,       // 9
-        Events.HAS_ALARM,            // 10
-        Events.ACCESS_LEVEL,         // 11
-        Events.COLOR,                // 12
-        Events.HAS_ATTENDEE_DATA,    // 13
-        Events.GUESTS_CAN_MODIFY,    // 14
-        // TODO Events.GUESTS_CAN_INVITE_OTHERS has not been implemented in calendar provider
-        Events.GUESTS_CAN_INVITE_OTHERS, // 15
-        Events.ORGANIZER,            // 16
-    };
-    private static final int EVENT_INDEX_ID = 0;
-    private static final int EVENT_INDEX_TITLE = 1;
-    private static final int EVENT_INDEX_RRULE = 2;
-    private static final int EVENT_INDEX_ALL_DAY = 3;
-    private static final int EVENT_INDEX_CALENDAR_ID = 4;
-    private static final int EVENT_INDEX_SYNC_ID = 6;
-    private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
-    private static final int EVENT_INDEX_DESCRIPTION = 8;
-    private static final int EVENT_INDEX_EVENT_LOCATION = 9;
-    private static final int EVENT_INDEX_HAS_ALARM = 10;
-    private static final int EVENT_INDEX_ACCESS_LEVEL = 11;
-    private static final int EVENT_INDEX_COLOR = 12;
-    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
-    private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 14;
-    private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 15;
-    private static final int EVENT_INDEX_ORGANIZER = 16;
-
-    private static final String[] ATTENDEES_PROJECTION = new String[] {
-        Attendees._ID,                      // 0
-        Attendees.ATTENDEE_NAME,            // 1
-        Attendees.ATTENDEE_EMAIL,           // 2
-        Attendees.ATTENDEE_RELATIONSHIP,    // 3
-        Attendees.ATTENDEE_STATUS,          // 4
-    };
-    private static final int ATTENDEES_INDEX_ID = 0;
-    private static final int ATTENDEES_INDEX_NAME = 1;
-    private static final int ATTENDEES_INDEX_EMAIL = 2;
-    private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
-    private static final int ATTENDEES_INDEX_STATUS = 4;
-
-    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
-
-    private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
-            + Attendees.ATTENDEE_EMAIL + " ASC";
-
-    static final String[] CALENDARS_PROJECTION = new String[] {
-        Calendars._ID,           // 0
-        Calendars.DISPLAY_NAME,  // 1
-        Calendars.OWNER_ACCOUNT, // 2
-        Calendars.ORGANIZER_CAN_RESPOND // 3
-    };
-    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
-    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
-    static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
-
-    static final String CALENDARS_WHERE = Calendars._ID + "=%d";
-    static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.DISPLAY_NAME + "=?";
-
-    private static final String[] REMINDERS_PROJECTION = new String[] {
-        Reminders._ID,      // 0
-        Reminders.MINUTES,  // 1
-    };
-    private static final int REMINDERS_INDEX_MINUTES = 1;
-    private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
-            Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
-            Reminders.METHOD_DEFAULT + ")";
-    private static final String REMINDERS_SORT = Reminders.MINUTES;
-
-    private static final int MENU_GROUP_REMINDER = 1;
-    private static final int MENU_GROUP_EDIT = 2;
-    private static final int MENU_GROUP_DELETE = 3;
-
-    private static final int MENU_ADD_REMINDER = 1;
-    private static final int MENU_EDIT = 2;
-    private static final int MENU_DELETE = 3;
-
-    private static final int ATTENDEE_ID_NONE = -1;
-    private static final int ATTENDEE_NO_RESPONSE = -1;
-    private static final int[] ATTENDEE_VALUES = {
-            ATTENDEE_NO_RESPONSE,
-            Attendees.ATTENDEE_STATUS_ACCEPTED,
-            Attendees.ATTENDEE_STATUS_TENTATIVE,
-            Attendees.ATTENDEE_STATUS_DECLINED,
-    };
-
-    private LinearLayout mRemindersContainer;
-    private LinearLayout mOrganizerContainer;
-    private TextView mOrganizerView;
-
-    private Uri mUri;
-    private long mEventId;
-    private Cursor mEventCursor;
-    private Cursor mAttendeesCursor;
-    private Cursor mCalendarsCursor;
-
-    private long mStartMillis;
-    private long mEndMillis;
-
-    private boolean mHasAttendeeData;
-    private boolean mIsOrganizer;
-    private long mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
-    private boolean mOrganizerCanRespond;
-    private String mCalendarOwnerAccount;
-    private boolean mCanModifyCalendar;
-    private boolean mIsBusyFreeCalendar;
-    private boolean mCanModifyEvent;
-    private int mNumOfAttendees;
-    private String mOrganizer;
-
-    private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
-    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
-    private ArrayList<Integer> mReminderValues;
-    private ArrayList<String> mReminderLabels;
-    private int mDefaultReminderMinutes;
-    private boolean mOriginalHasAlarm;
+    static final int ATTENDEE_NO_RESPONSE = -1;
 
     private DeleteEventHelper mDeleteEventHelper;
-    private EditResponseHelper mEditResponseHelper;
-
-    private int mResponseOffset;
-    private int mOriginalAttendeeResponse;
-    private int mAttendeeResponseFromIntent = ATTENDEE_NO_RESPONSE;
-    private boolean mIsRepeating;
-    private boolean mIsDuplicateName;
-
-    private Pattern mWildcardPattern = Pattern.compile("^.*$");
-    private LayoutInflater mLayoutInflater;
-    private LinearLayout mReminderAdder;
-
-    // TODO This can be removed when the contacts content provider doesn't return duplicates
-    private int mUpdateCounts;
-    private static class ViewHolder {
-        QuickContactBadge badge;
-        ImageView presence;
-        int updateCounts;
-    }
-    private HashMap<String, ViewHolder> mViewHolders = new HashMap<String, ViewHolder>();
-    private PresenceQueryHandler mPresenceQueryHandler;
-
-    private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI;
-
-    int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0;
-    int PRESENCE_PROJECTION_PRESENCE_INDEX = 1;
-    int PRESENCE_PROJECTION_EMAIL_INDEX = 2;
-    int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3;
-
-    private static final String[] PRESENCE_PROJECTION = new String[] {
-        Email.CONTACT_ID,           // 0
-        Email.CONTACT_PRESENCE,     // 1
-        Email.DATA,                 // 2
-        Email.PHOTO_ID,             // 3
-    };
-
-    ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
-    ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
-    ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
-    ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
-    private int mColor;
-
-    // This gets run if the time zone is updated in the db
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            if (EventInfoActivity.this.isFinishing()) {
-                return;
-            }
-            updateView();
-        }
-    };
-
-    // This is called when one of the "remove reminder" buttons is selected.
-    public void onClick(View v) {
-        LinearLayout reminderItem = (LinearLayout) v.getParent();
-        if (reminderItem == null) {
-            return;
-        }
-        LinearLayout parent = (LinearLayout) reminderItem.getParent();
-        if (parent == null) {
-            return;
-        }
-        parent.removeView(reminderItem);
-        mReminderItems.remove(reminderItem);
-        updateRemindersVisibility();
-    }
-
-    public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
-        // If they selected the "No response" option, then don't display the
-        // dialog asking which events to change.
-        if (id == 0 && mResponseOffset == 0) {
-            return;
-        }
-
-        // If this is not a repeating event, then don't display the dialog
-        // asking which events to change.
-        if (!mIsRepeating) {
-            return;
-        }
-
-        // If the selection is the same as the original, then don't display the
-        // dialog asking which events to change.
-        int index = findResponseIndexFor(mOriginalAttendeeResponse);
-        if (position == index + mResponseOffset) {
-            return;
-        }
-
-        // This is a repeating event. We need to ask the user if they mean to
-        // change just this one instance or all instances.
-        mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
-    }
-
-    public void onNothingSelected(AdapterView<?> parent) {
-    }
+    private CalendarController mController;
 
     @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // This needs to be created before setContentView
+        mController = CalendarController.getInstance(this);
 
-        // Event cursor
         Intent intent = getIntent();
-        mUri = intent.getData();
-        ContentResolver cr = getContentResolver();
-        mStartMillis = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
-        mEndMillis = intent.getLongExtra(EVENT_END_TIME, 0);
-        mAttendeeResponseFromIntent = intent.getIntExtra(ATTENDEE_STATUS, ATTENDEE_NO_RESPONSE);
-        mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null, null);
-        if (initEventCursor()) {
-            // The cursor is empty. This can happen if the event was deleted.
-            finish();
-            return;
-        }
+        Uri uri = intent.getData();
+        long startMillis = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
+        long endMillis = intent.getLongExtra(EVENT_END_TIME, 0);
+        int attendeeResponseFromIntent = intent.getIntExtra(
+                ATTENDEE_STATUS, ATTENDEE_NO_RESPONSE);
+        Fragment f = new EventInfoFragment(
+                uri, startMillis, endMillis, attendeeResponseFromIntent);
+        getFragmentManager().openTransaction().add(android.R.id.content, f).commit();
 
-        setContentView(R.layout.event_info_activity);
-        mPresenceQueryHandler = new PresenceQueryHandler(this, cr);
-        mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container);
-        mOrganizerContainer = (LinearLayout) findViewById(R.id.organizer_container);
-        mOrganizerView = (TextView) findViewById(R.id.organizer);
-
-        // Calendars cursor
-        Uri uri = Calendars.CONTENT_URI;
-        String where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID));
-        mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null, null);
-        mCalendarOwnerAccount = "";
-        if (mCalendarsCursor != null) {
-            mCalendarsCursor.moveToFirst();
-            String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-            mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
-            mOrganizerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
-
-            String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
-            mIsDuplicateName = isDuplicateName(displayName);
-        }
-        String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
-        mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(eventOrganizer);
-        mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
-
-        updateView();
-
-        // Attendees cursor
-        uri = Attendees.CONTENT_URI;
-        where = String.format(ATTENDEES_WHERE, mEventId);
-        mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, null,
-                ATTENDEES_SORT_ORDER);
-        initAttendeesCursor();
-
-        mOrganizer = eventOrganizer;
-        mCanModifyCalendar =
-                mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS;
-        mIsBusyFreeCalendar =
-                mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS;
-
-        mCanModifyEvent = mCanModifyCalendar
-                && (mIsOrganizer || (mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0));
-
-        // Initialize the reminder values array.
-        Resources r = getResources();
-        String[] strings = r.getStringArray(R.array.reminder_minutes_values);
-        int size = strings.length;
-        ArrayList<Integer> list = new ArrayList<Integer>(size);
-        for (int i = 0 ; i < size ; i++) {
-            list.add(Integer.parseInt(strings[i]));
-        }
-        mReminderValues = list;
-        String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
-        mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
-
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
-        String durationString =
-                prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
-        mDefaultReminderMinutes = Integer.parseInt(durationString);
-
-        // Reminders cursor
-        boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
-        if (hasAlarm) {
-            uri = Reminders.CONTENT_URI;
-            where = String.format(REMINDERS_WHERE, mEventId);
-            Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null,
-                    REMINDERS_SORT);
-            try {
-                // First pass: collect all the custom reminder minutes (e.g.,
-                // a reminder of 8 minutes) into a global list.
-                while (reminderCursor.moveToNext()) {
-                    int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
-                    EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes);
-                }
-
-                // Second pass: create the reminder spinners
-                reminderCursor.moveToPosition(-1);
-                while (reminderCursor.moveToNext()) {
-                    int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
-                    mOriginalMinutes.add(minutes);
-                    EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
-                            mReminderLabels, minutes);
-                }
-            } finally {
-                reminderCursor.close();
-            }
-        }
-        mOriginalHasAlarm = hasAlarm;
-
-        // Setup the + Add Reminder Button
-        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
-            public void onClick(View v) {
-                addReminder();
-            }
-        };
-        ImageButton reminderAddButton = (ImageButton) findViewById(R.id.reminder_add);
-        reminderAddButton.setOnClickListener(addReminderOnClickListener);
-
-        mReminderAdder = (LinearLayout) findViewById(R.id.reminder_adder);
-        updateRemindersVisibility();
-
-        mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
-        mEditResponseHelper = new EditResponseHelper(this);
+        mDeleteEventHelper = new DeleteEventHelper(this, this, true /* exit when done */);
+        mController.registerEventHandler(HANDLER_KEY, this);
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
-        if (initEventCursor()) {
-            // The cursor is empty. This can happen if the event was deleted.
-            finish();
-            return;
-        }
-        initCalendarsCursor();
-        updateResponse();
-        updateTitle();
+    protected void onDestroy() {
+        super.onDestroy();
+        CalendarController.removeInstance(this);
     }
 
-    private void updateTitle() {
-        Resources res = getResources();
-        if (mCanModifyCalendar && !mIsOrganizer) {
-            setTitle(res.getString(R.string.event_info_title_invite));
-        } else {
-            setTitle(res.getString(R.string.event_info_title));
-        }
+    @Override
+    public void eventsChanged() {
+        // TODO Auto-generated method stub
+
     }
 
-    boolean isDuplicateName(String displayName) {
-        Cursor dupNameCursor = managedQuery(Calendars.CONTENT_URI, CALENDARS_PROJECTION,
-                CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
-        boolean isDuplicateName = false;
-        if(dupNameCursor != null) {
-            if (dupNameCursor.getCount() > 1) {
-                isDuplicateName = true;
-            }
-            dupNameCursor.close();
-        }
-        return isDuplicateName;
-    }
-
-    /**
-     * Initializes the event cursor, which is expected to point to the first
-     * (and only) result from a query.
-     * @return true if the cursor is empty.
-     */
-    private boolean initEventCursor() {
-        if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
-            return true;
-        }
-        mEventCursor.moveToFirst();
-        mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
-        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
-        mIsRepeating = (rRule != null);
+    @Override
+    public boolean getAllDay() {
+        // TODO Auto-generated method stub
         return false;
     }
 
-    private static class Attendee {
-        String mName;
-        String mEmail;
-
-        Attendee(String name, String email) {
-            mName = name;
-            mEmail = email;
-        }
-    }
-
-    @SuppressWarnings("fallthrough")
-    private void initAttendeesCursor() {
-        mOriginalAttendeeResponse = ATTENDEE_NO_RESPONSE;
-        mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
-        mNumOfAttendees = 0;
-        if (mAttendeesCursor != null) {
-            mNumOfAttendees = mAttendeesCursor.getCount();
-            if (mAttendeesCursor.moveToFirst()) {
-                mAcceptedAttendees.clear();
-                mDeclinedAttendees.clear();
-                mTentativeAttendees.clear();
-                mNoResponseAttendees.clear();
-
-                do {
-                    int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
-                    String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
-                    String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
-
-                    if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) ==
-                            Attendees.RELATIONSHIP_ORGANIZER) {
-                        // Overwrites the one from Event table if available
-                        if (name != null && name.length() > 0) {
-                            mOrganizer = name;
-                        } else if (email != null && email.length() > 0) {
-                            mOrganizer = email;
-                        }
-                    }
-
-                    if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE &&
-                            mCalendarOwnerAccount.equalsIgnoreCase(email)) {
-                        mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
-                        mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
-                    } else {
-                        // Don't show your own status in the list because:
-                        //  1) it doesn't make sense for event without other guests.
-                        //  2) there's a spinner for that for events with guests.
-                        switch(status) {
-                            case Attendees.ATTENDEE_STATUS_ACCEPTED:
-                                mAcceptedAttendees.add(new Attendee(name, email));
-                                break;
-                            case Attendees.ATTENDEE_STATUS_DECLINED:
-                                mDeclinedAttendees.add(new Attendee(name, email));
-                                break;
-                            case Attendees.ATTENDEE_STATUS_NONE:
-                                mNoResponseAttendees.add(new Attendee(name, email));
-                                // Fallthrough so that no response is a subset of tentative
-                            default:
-                                mTentativeAttendees.add(new Attendee(name, email));
-                        }
-                    }
-                } while (mAttendeesCursor.moveToNext());
-                mAttendeesCursor.moveToFirst();
-
-                updateAttendees();
-            }
-        }
-        // only show the organizer if we're not the organizer and if
-        // we have attendee data (might have been removed by the server
-        // for events with a lot of attendees).
-        if (!mIsOrganizer && mHasAttendeeData) {
-            mOrganizerContainer.setVisibility(View.VISIBLE);
-            mOrganizerView.setText(mOrganizer);
-        } else {
-            mOrganizerContainer.setVisibility(View.GONE);
-        }
-    }
-
-    private void initCalendarsCursor() {
-        if (mCalendarsCursor != null) {
-            mCalendarsCursor.moveToFirst();
-        }
-    }
-
     @Override
-    public void onPause() {
-        super.onPause();
-
-        ContentResolver cr = getContentResolver();
-        ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems,
-                mReminderValues);
-        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
-        boolean changed = EditEvent.saveReminders(ops, mEventId, reminderMinutes, mOriginalMinutes,
-                false /* no force save */);
-        try {
-            // TODO Run this in a background process.
-            cr.applyBatch(Calendars.CONTENT_URI.getAuthority(), ops);
-            // Update the "hasAlarm" field for the event
-            Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
-            int len = reminderMinutes.size();
-            boolean hasAlarm = len > 0;
-            if (hasAlarm != mOriginalHasAlarm) {
-                ContentValues values = new ContentValues();
-                values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
-                cr.update(uri, values, null, null);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Ignoring exception: ", e);
-        } catch (OperationApplicationException e) {
-            Log.w(TAG, "Ignoring exception: ", e);
-        }
-
-        changed |= saveResponse(cr);
-        if (changed) {
-            Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuItem item;
-        item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
-                R.string.add_new_reminder);
-        item.setIcon(R.drawable.ic_menu_reminder);
-        item.setAlphabeticShortcut('r');
-
-        item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label);
-        item.setIcon(android.R.drawable.ic_menu_edit);
-        item.setAlphabeticShortcut('e');
-
-        item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label);
-        item.setIcon(android.R.drawable.ic_menu_delete);
-
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        boolean canAddReminders = canAddReminders();
-        menu.setGroupVisible(MENU_GROUP_REMINDER, canAddReminders);
-        menu.setGroupEnabled(MENU_GROUP_REMINDER, canAddReminders);
-
-        menu.setGroupVisible(MENU_GROUP_EDIT, mCanModifyEvent);
-        menu.setGroupEnabled(MENU_GROUP_EDIT, mCanModifyEvent);
-        menu.setGroupVisible(MENU_GROUP_DELETE, mCanModifyCalendar);
-        menu.setGroupEnabled(MENU_GROUP_DELETE, mCanModifyCalendar);
-
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    private boolean canAddReminders() {
-        return !mIsBusyFreeCalendar && mReminderItems.size() < MAX_REMINDERS;
-    }
-
-    private void addReminder() {
-        // TODO: when adding a new reminder, make it different from the
-        // last one in the list (if any).
-        if (mDefaultReminderMinutes == 0) {
-            EditEvent.addReminder(this, this, mReminderItems,
-                    mReminderValues, mReminderLabels, 10 /* minutes */);
-        } else {
-            EditEvent.addReminder(this, this, mReminderItems,
-                    mReminderValues, mReminderLabels, mDefaultReminderMinutes);
-        }
-        updateRemindersVisibility();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        super.onOptionsItemSelected(item);
-        switch (item.getItemId()) {
-        case MENU_ADD_REMINDER:
-            addReminder();
-            break;
-        case MENU_EDIT:
-            doEdit();
-            break;
-        case MENU_DELETE:
-            doDelete();
-            break;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_DEL) {
-            doDelete();
-            return true;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    private void updateRemindersVisibility() {
-        if (mIsBusyFreeCalendar) {
-            mRemindersContainer.setVisibility(View.GONE);
-        } else {
-            mRemindersContainer.setVisibility(View.VISIBLE);
-            mReminderAdder.setVisibility(canAddReminders() ? View.VISIBLE : View.GONE);
-        }
-    }
-
-    /**
-     * Saves the response to an invitation if the user changed the response.
-     * Returns true if the database was updated.
-     *
-     * @param cr the ContentResolver
-     * @return true if the database was changed
-     */
-    private boolean saveResponse(ContentResolver cr) {
-        if (mAttendeesCursor == null || mEventCursor == null) {
-            return false;
-        }
-        Spinner spinner = (Spinner) findViewById(R.id.response_value);
-        int position = spinner.getSelectedItemPosition() - mResponseOffset;
-        if (position <= 0) {
-            return false;
-        }
-
-        int status = ATTENDEE_VALUES[position];
-
-        // If the status has not changed, then don't update the database
-        if (status == mOriginalAttendeeResponse) {
-            return false;
-        }
-
-        // If we never got an owner attendee id we can't set the status
-        if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE) {
-            return false;
-        }
-
-        // Update the locally cached values
-        mAttendeeResponseFromIntent = mOriginalAttendeeResponse = status;
-
-        if (!mIsRepeating) {
-            // This is a non-repeating event
-            updateResponse(cr, mEventId, mCalendarOwnerAttendeeId, status);
-            return true;
-        }
-
-        // This is a repeating event
-        int whichEvents = mEditResponseHelper.getWhichEvents();
-        switch (whichEvents) {
-            case -1:
-                return false;
-            case UPDATE_SINGLE:
-                createExceptionResponse(cr, mEventId, mCalendarOwnerAttendeeId, status);
-                return true;
-            case UPDATE_ALL:
-                updateResponse(cr, mEventId, mCalendarOwnerAttendeeId, status);
-                return true;
-            default:
-                Log.e(TAG, "Unexpected choice for updating invitation response");
-                break;
-        }
-        return false;
-    }
-
-    private void updateResponse(ContentResolver cr, long eventId, long attendeeId, int status) {
-        // Update the attendee status in the attendees table.  the provider
-        // takes care of updating the self attendance status.
-        ContentValues values = new ContentValues();
-
-        if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
-            values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
-        }
-        values.put(Attendees.ATTENDEE_STATUS, status);
-        values.put(Attendees.EVENT_ID, eventId);
-
-        Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
-        cr.update(uri, values, null /* where */, null /* selection args */);
-    }
-
-    private void createExceptionResponse(ContentResolver cr, long eventId,
-            long attendeeId, int status) {
-        // Fetch information about the repeating event.
-        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        Cursor cursor = cr.query(uri, EVENT_PROJECTION, null, null, null);
-        if (cursor == null) {
-            return;
-        }
-        if(!cursor.moveToFirst()) {
-            cursor.close();
-            return;
-        }
-
-        try {
-            ContentValues values = new ContentValues();
-
-            String title = cursor.getString(EVENT_INDEX_TITLE);
-            String timezone = cursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
-            int calendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID);
-            boolean allDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
-            String syncId = cursor.getString(EVENT_INDEX_SYNC_ID);
-
-            values.put(Events.TITLE, title);
-            values.put(Events.EVENT_TIMEZONE, timezone);
-            values.put(Events.ALL_DAY, allDay ? 1 : 0);
-            values.put(Events.CALENDAR_ID, calendarId);
-            values.put(Events.DTSTART, mStartMillis);
-            values.put(Events.DTEND, mEndMillis);
-            values.put(Events.ORIGINAL_EVENT, syncId);
-            values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
-            values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
-            values.put(Events.STATUS, Events.STATUS_CONFIRMED);
-            values.put(Events.SELF_ATTENDEE_STATUS, status);
-
-            // Create a recurrence exception
-            cr.insert(Events.CONTENT_URI, values);
-        } finally {
-            cursor.close();
-        }
-    }
-
-    private int findResponseIndexFor(int response) {
-        int size = ATTENDEE_VALUES.length;
-        for (int index = 0; index < size; index++) {
-            if (ATTENDEE_VALUES[index] == response) {
-                return index;
-            }
-        }
+    public long getSelectedTime() {
+        // TODO Auto-generated method stub
         return 0;
     }
 
-    private void doEdit() {
-        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
-        Intent intent = new Intent(Intent.ACTION_EDIT, uri);
-        intent.putExtra(Calendar.EVENT_BEGIN_TIME, mStartMillis);
-        intent.putExtra(Calendar.EVENT_END_TIME, mEndMillis);
-        intent.setClass(EventInfoActivity.this, EditEvent.class);
-        startActivity(intent);
-        finish();
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.DELETE_EVENT;
     }
 
-    private void doDelete() {
-        mDeleteEventHelper.delete(mStartMillis, mEndMillis, mEventCursor, -1);
+    @Override
+    public void goTo(Time time, boolean animate) {
+        // TODO Auto-generated method stub
+
     }
 
-    private void updateView() {
-        if (mEventCursor == null) {
-            return;
-        }
+    @Override
+    public void goToToday() {
+        // TODO Auto-generated method stub
 
-        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
-        if (eventName == null || eventName.length() == 0) {
-            Resources res = getResources();
-            eventName = res.getString(R.string.no_title_label);
-        }
+    }
 
-        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
-        String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
-        String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
-        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
-        boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
-        String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
-        mColor = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
-
-        View calBackground = findViewById(R.id.cal_background);
-        calBackground.setBackgroundColor(mColor);
-
-        TextView title = (TextView) findViewById(R.id.title);
-        title.setTextColor(mColor);
-
-        View divider = findViewById(R.id.divider);
-        divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
-
-        // What
-        if (eventName != null) {
-            setTextCommon(R.id.title, eventName);
-        }
-
-        // When
-        String when;
-        int flags;
-        if (allDay) {
-            flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE;
-        } else {
-            flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
-            if (DateFormat.is24HourFormat(this)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-        }
-        when = Utils.formatDateRange(this, mStartMillis, mEndMillis, flags);
-        setTextCommon(R.id.when, when);
-
-        // Show the event timezone if it is different from the local timezone
-        String localTimezone = Utils.getTimeZone(this, mUpdateTZ);
-        if (allDay) {
-            localTimezone = Time.TIMEZONE_UTC;
-        }
-        if (eventTimezone != null && !allDay &&
-                (!TextUtils.equals(localTimezone, eventTimezone) ||
-                !TextUtils.equals(localTimezone, Time.getCurrentTimezone()))) {
-            String displayName;
-            TimeZone tz = TimeZone.getTimeZone(localTimezone);
-            if (tz == null || tz.getID().equals("GMT")) {
-                displayName = localTimezone;
-            } else {
-                displayName = tz.getDisplayName();
-            }
-
-            setTextCommon(R.id.timezone, displayName);
-        } else {
-            setVisibilityCommon(R.id.timezone_container, View.GONE);
-        }
-
-        // Repeat
-        if (rRule != null) {
-            EventRecurrence eventRecurrence = new EventRecurrence();
-            eventRecurrence.parse(rRule);
-            Time date = new Time(Utils.getTimeZone(this, mUpdateTZ));
-            if (allDay) {
-                date.timezone = Time.TIMEZONE_UTC;
-            }
-            date.set(mStartMillis);
-            eventRecurrence.setStartDate(date);
-            String repeatString = EventRecurrenceFormatter.getRepeatString(getResources(),
-                    eventRecurrence);
-            setTextCommon(R.id.repeat, repeatString);
-        } else {
-            setVisibilityCommon(R.id.repeat_container, View.GONE);
-        }
-
-        // Where
-        if (location == null || location.length() == 0) {
-            setVisibilityCommon(R.id.where, View.GONE);
-        } else {
-            final TextView textView = (TextView) findViewById(R.id.where);
-            if (textView != null) {
-                    textView.setAutoLinkMask(0);
-                    textView.setText(location);
-                    Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q=");
-                    textView.setOnTouchListener(new OnTouchListener() {
-                        public boolean onTouch(View v, MotionEvent event) {
-                            try {
-                                return v.onTouchEvent(event);
-                            } catch (ActivityNotFoundException e) {
-                                // ignore
-                                return true;
-                            }
-                        }
-                    });
-            }
-        }
-
-        // Description
-        if (description == null || description.length() == 0) {
-            setVisibilityCommon(R.id.description, View.GONE);
-        } else {
-            setTextCommon(R.id.description, description);
-        }
-
-        // Calendar
-        if (mCalendarsCursor != null) {
-            String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
-            String ownerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
-            if (mIsDuplicateName && !calendarName.equalsIgnoreCase(ownerAccount)) {
-                Resources res = getResources();
-                TextView ownerText = (TextView) findViewById(R.id.owner);
-                ownerText.setText(ownerAccount);
-                ownerText.setTextColor(res.getColor(R.color.calendar_owner_text_color));
-            } else {
-                setVisibilityCommon(R.id.owner, View.GONE);
-            }
-            setTextCommon(R.id.calendar, calendarName);
-        } else {
-            setVisibilityCommon(R.id.calendar_container, View.GONE);
+    @Override
+    public void handleEvent(EventInfo event) {
+        if (event.eventType == EventType.DELETE_EVENT) {
+            long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false);
+            mDeleteEventHelper.delete(
+                    event.startTime.toMillis(false), endTime, event.id, -1);
         }
     }
 
-    private void updateAttendees() {
-        LinearLayout attendeesLayout = (LinearLayout) findViewById(R.id.attendee_list);
-        attendeesLayout.removeAllViewsInLayout();
-        ++mUpdateCounts;
-        if(mAcceptedAttendees.size() == 0 && mDeclinedAttendees.size() == 0 &&
-                mTentativeAttendees.size() == mNoResponseAttendees.size()) {
-            // If all guests have no response just list them as guests,
-            CharSequence guestsLabel = getResources().getText(R.string.attendees_label);
-            addAttendeesToLayout(mNoResponseAttendees, attendeesLayout, guestsLabel);
-        } else {
-            // If we have any responses then divide them up by response
-            CharSequence[] entries;
-            entries = getResources().getTextArray(R.array.response_labels2);
-            addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]);
-            addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]);
-            addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]);
-        }
-    }
-
-    private void addAttendeesToLayout(ArrayList<Attendee> attendees, LinearLayout attendeeList,
-            CharSequence sectionTitle) {
-        if (attendees.size() == 0) {
-            return;
-        }
-
-        // Yes/No/Maybe Title
-        View titleView = mLayoutInflater.inflate(R.layout.contact_item, null);
-        titleView.findViewById(R.id.badge).setVisibility(View.GONE);
-        View divider = titleView.findViewById(R.id.separator);
-        divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
-
-        TextView title = (TextView) titleView.findViewById(R.id.name);
-        title.setText(getString(R.string.response_label, sectionTitle, attendees.size()));
-        title.setTextAppearance(this, R.style.TextAppearance_EventInfo_Label);
-        attendeeList.addView(titleView);
-
-        // Attendees
-        int numOfAttendees = attendees.size();
-        StringBuilder selection = new StringBuilder(Email.DATA + " IN (");
-        String[] selectionArgs = new String[numOfAttendees];
-
-        for (int i = 0; i < numOfAttendees; ++i) {
-            Attendee attendee = attendees.get(i);
-            selectionArgs[i] = attendee.mEmail;
-
-            View v = mLayoutInflater.inflate(R.layout.contact_item, null);
-            v.setTag(attendee);
-
-            View separator = v.findViewById(R.id.separator);
-            separator.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
-
-            // Text
-            TextView tv = (TextView) v.findViewById(R.id.name);
-            String name = attendee.mName;
-            if (name == null || name.length() == 0) {
-                name = attendee.mEmail;
-            }
-            tv.setText(name);
-
-            ViewHolder vh = new ViewHolder();
-            vh.badge = (QuickContactBadge) v.findViewById(R.id.badge);
-            vh.badge.assignContactFromEmail(attendee.mEmail, true);
-            vh.presence = (ImageView) v.findViewById(R.id.presence);
-            mViewHolders.put(attendee.mEmail, vh);
-
-            if (i == 0) {
-                selection.append('?');
-            } else {
-                selection.append(", ?");
-            }
-
-            attendeeList.addView(v);
-        }
-        selection.append(')');
-
-        mPresenceQueryHandler.startQuery(mUpdateCounts, attendees, CONTACT_DATA_WITH_PRESENCE_URI,
-                PRESENCE_PROJECTION, selection.toString(), selectionArgs, null);
-    }
-
-    private class PresenceQueryHandler extends AsyncQueryHandler {
-        Context mContext;
-
-        public PresenceQueryHandler(Context context, ContentResolver cr) {
-            super(cr);
-            mContext = context;
-        }
-
-        @Override
-        protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) {
-            if (cursor == null) {
-                if (DEBUG) {
-                    Log.e(TAG, "onQueryComplete: cursor == null");
-                }
-                return;
-            }
-
-            try {
-                cursor.moveToPosition(-1);
-                while (cursor.moveToNext()) {
-                    String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX);
-                    int contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX);
-                    ViewHolder vh = mViewHolders.get(email);
-                    int photoId = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX);
-                    if (DEBUG) {
-                        Log.e(TAG, "onQueryComplete Id: " + contactId + " PhotoId: " + photoId
-                                + " Email: " + email);
-                    }
-                    if (vh == null) {
-                        continue;
-                    }
-                    ImageView presenceView = vh.presence;
-                    if (presenceView != null) {
-                        int status = cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX);
-                        presenceView.setImageResource(Presence.getPresenceIconResourceId(status));
-                        presenceView.setVisibility(View.VISIBLE);
-                    }
-
-                    if (photoId > 0 && vh.updateCounts < queryIndex) {
-                        vh.updateCounts = queryIndex;
-                        Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
-
-                        // TODO, modify to batch queries together
-                        ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(mContext, vh.badge,
-                                personUri, R.drawable.ic_contact_picture);
-                    }
-                }
-            } finally {
-                cursor.close();
-            }
-        }
-    }
-
-    void updateResponse() {
-        // we only let the user accept/reject/etc. a meeting if:
-        // a) you can edit the event's containing calendar AND
-        // b) you're not the organizer and only attendee AND
-        // c) organizerCanRespond is enabled for the calendar
-        // (if the attendee data has been hidden, the visible number of attendees
-        // will be 1 -- the calendar owner's).
-        // (there are more cases involved to be 100% accurate, such as
-        // paying attention to whether or not an attendee status was
-        // included in the feed, but we're currently omitting those corner cases
-        // for simplicity).
-        if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
-                (mIsOrganizer && !mOrganizerCanRespond)) {
-            setVisibilityCommon(R.id.response_container, View.GONE);
-            return;
-        }
-
-        setVisibilityCommon(R.id.response_container, View.VISIBLE);
-
-        Spinner spinner = (Spinner) findViewById(R.id.response_value);
-
-        mResponseOffset = 0;
-
-        /* If the user has previously responded to this event
-         * we should not allow them to select no response again.
-         * Switch the entries to a set of entries without the
-         * no response option.
-         */
-        if ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED)
-                && (mOriginalAttendeeResponse != ATTENDEE_NO_RESPONSE)
-                && (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) {
-            CharSequence[] entries;
-            entries = getResources().getTextArray(R.array.response_labels2);
-            mResponseOffset = -1;
-            ArrayAdapter<CharSequence> adapter =
-                new ArrayAdapter<CharSequence>(this,
-                        android.R.layout.simple_spinner_item, entries);
-            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-            spinner.setAdapter(adapter);
-        }
-
-        int index;
-        if (mAttendeeResponseFromIntent != ATTENDEE_NO_RESPONSE) {
-            index = findResponseIndexFor(mAttendeeResponseFromIntent);
-        } else {
-            index = findResponseIndexFor(mOriginalAttendeeResponse);
-        }
-        spinner.setSelection(index + mResponseOffset);
-        spinner.setOnItemSelectedListener(this);
-    }
-
-    private void setTextCommon(int id, CharSequence text) {
-        TextView textView = (TextView) findViewById(id);
-        if (textView == null)
-            return;
-        textView.setText(text);
-    }
-
-    private void setVisibilityCommon(int id, int visibility) {
-        View v = findViewById(id);
-        if (v != null) {
-            v.setVisibility(visibility);
-        }
-        return;
-    }
-
-    /**
-     * Taken from com.google.android.gm.HtmlConversationActivity
-     *
-     * Send the intent that shows the Contact info corresponding to the email address.
-     */
-    public void showContactInfo(Attendee attendee, Rect rect) {
-        // First perform lookup query to find existing contact
-        final ContentResolver resolver = getContentResolver();
-        final String address = attendee.mEmail;
-        final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
-                Uri.encode(address));
-        final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
-
-        if (lookupUri != null) {
-            // Found matching contact, trigger QuickContact
-            QuickContact.showQuickContact(this, rect, lookupUri, QuickContact.MODE_MEDIUM, null);
-        } else {
-            // No matching contact, ask user to create one
-            final Uri mailUri = Uri.fromParts("mailto", address, null);
-            final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
-
-            // Pass along full E-mail string for possible create dialog
-            Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
-            intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
-
-            // Only provide personal name hint if we have one
-            final String senderPersonal = attendee.mName;
-            if (!TextUtils.isEmpty(senderPersonal)) {
-                intent.putExtra(Intents.Insert.NAME, senderPersonal);
-            }
-
-            startActivity(intent);
-        }
-    }
 }
diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java
new file mode 100644
index 0000000..4c83f89
--- /dev/null
+++ b/src/com/android/calendar/EventInfoFragment.java
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.event.EditEventHelper;
+import com.android.calendar.event.EventViewUtils;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ActivityNotFoundException;
+import android.content.AsyncQueryHandler;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.EventRecurrence;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.QuickContact;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.util.Linkify;
+import android.text.util.Rfc822Token;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.QuickContactBadge;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+public class EventInfoFragment extends DialogFragment implements View.OnClickListener,
+        AdapterView.OnItemSelectedListener, CalendarController.EventHandler {
+    public static final boolean DEBUG = false;
+
+    public static final String TAG = "EventInfoActivity";
+
+    private static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
+
+    private static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
+
+    private static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
+
+    private static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
+
+    private static final int MAX_REMINDERS = 5;
+
+    /**
+     * These are the corresponding indices into the array of strings
+     * "R.array.change_response_labels" in the resource file.
+     */
+    static final int UPDATE_SINGLE = 0;
+    static final int UPDATE_ALL = 1;
+
+    // Query tokens for QueryHandler
+    private static final int TOKEN_QUERY_EVENT = 0;
+    private static final int TOKEN_QUERY_CALENDARS = 1;
+    private static final int TOKEN_QUERY_ATTENDEES = 2;
+    private static final int TOKEN_QUERY_REMINDERS = 3;
+    private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 4;
+
+    private static final String[] EVENT_PROJECTION = new String[] {
+        Events._ID,                  // 0  do not remove; used in DeleteEventHelper
+        Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
+        Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
+        Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
+        Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
+        Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
+        Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
+        Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
+        Events.DESCRIPTION,          // 8
+        Events.EVENT_LOCATION,       // 9
+        Events.HAS_ALARM,            // 10
+        Calendars.ACCESS_LEVEL,      // 11
+        Calendars.COLOR,             // 12
+        Events.HAS_ATTENDEE_DATA,    // 13
+        Events.GUESTS_CAN_MODIFY,    // 14
+        // TODO Events.GUESTS_CAN_INVITE_OTHERS has not been implemented in calendar provider
+        Events.GUESTS_CAN_INVITE_OTHERS, // 15
+        Events.ORGANIZER,            // 16
+        Events.ORIGINAL_EVENT        // 17 do not remove; used in DeleteEventHelper
+    };
+    private static final int EVENT_INDEX_ID = 0;
+    private static final int EVENT_INDEX_TITLE = 1;
+    private static final int EVENT_INDEX_RRULE = 2;
+    private static final int EVENT_INDEX_ALL_DAY = 3;
+    private static final int EVENT_INDEX_CALENDAR_ID = 4;
+    private static final int EVENT_INDEX_SYNC_ID = 6;
+    private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
+    private static final int EVENT_INDEX_DESCRIPTION = 8;
+    private static final int EVENT_INDEX_EVENT_LOCATION = 9;
+    private static final int EVENT_INDEX_HAS_ALARM = 10;
+    private static final int EVENT_INDEX_ACCESS_LEVEL = 11;
+    private static final int EVENT_INDEX_COLOR = 12;
+    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
+    private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 14;
+    private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 15;
+    private static final int EVENT_INDEX_ORGANIZER = 16;
+
+    private static final String[] ATTENDEES_PROJECTION = new String[] {
+        Attendees._ID,                      // 0
+        Attendees.ATTENDEE_NAME,            // 1
+        Attendees.ATTENDEE_EMAIL,           // 2
+        Attendees.ATTENDEE_RELATIONSHIP,    // 3
+        Attendees.ATTENDEE_STATUS,          // 4
+    };
+    private static final int ATTENDEES_INDEX_ID = 0;
+    private static final int ATTENDEES_INDEX_NAME = 1;
+    private static final int ATTENDEES_INDEX_EMAIL = 2;
+    private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
+    private static final int ATTENDEES_INDEX_STATUS = 4;
+
+    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
+
+    private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
+            + Attendees.ATTENDEE_EMAIL + " ASC";
+
+    static final String[] CALENDARS_PROJECTION = new String[] {
+        Calendars._ID,           // 0
+        Calendars.DISPLAY_NAME,  // 1
+        Calendars.OWNER_ACCOUNT, // 2
+        Calendars.ORGANIZER_CAN_RESPOND // 3
+    };
+    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
+    static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
+
+    static final String CALENDARS_WHERE = Calendars._ID + "=?";
+    static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.DISPLAY_NAME + "=?";
+
+    private static final String[] REMINDERS_PROJECTION = new String[] {
+        Reminders._ID,      // 0
+        Reminders.MINUTES,  // 1
+    };
+    private static final int REMINDERS_INDEX_MINUTES = 1;
+    private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=? AND (" +
+            Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
+            Reminders.METHOD_DEFAULT + ")";
+    private static final String REMINDERS_SORT = Reminders.MINUTES;
+
+    private static final int MENU_GROUP_REMINDER = 1;
+    private static final int MENU_GROUP_EDIT = 2;
+    private static final int MENU_GROUP_DELETE = 3;
+
+    private static final int MENU_ADD_REMINDER = 1;
+    private static final int MENU_EDIT = 2;
+    private static final int MENU_DELETE = 3;
+
+    private View mView;
+    private LinearLayout mRemindersContainer;
+    private LinearLayout mOrganizerContainer;
+    private TextView mOrganizerView;
+
+    private Uri mUri;
+    private long mEventId;
+    private Cursor mEventCursor;
+    private Cursor mAttendeesCursor;
+    private Cursor mCalendarsCursor;
+
+    private long mStartMillis;
+    private long mEndMillis;
+
+    private boolean mHasAttendeeData;
+    private boolean mIsOrganizer;
+    private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
+    private boolean mOrganizerCanRespond;
+    private String mCalendarOwnerAccount;
+    private boolean mCanModifyCalendar;
+    private boolean mIsBusyFreeCalendar;
+    private boolean mCanModifyEvent;
+    private int mNumOfAttendees;
+    private String mOrganizer;
+
+    private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
+    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
+    private ArrayList<Integer> mReminderValues;
+    private ArrayList<String> mReminderLabels;
+    private int mDefaultReminderMinutes;
+    private boolean mOriginalHasAlarm;
+
+    private EditResponseHelper mEditResponseHelper;
+
+    private int mResponseOffset;
+    private int mOriginalAttendeeResponse;
+    private int mAttendeeResponseFromIntent = EditEventHelper.ATTENDEE_NO_RESPONSE;
+    private boolean mIsRepeating;
+    private boolean mIsDuplicateName;
+
+    private Pattern mWildcardPattern = Pattern.compile("^.*$");
+    private LayoutInflater mLayoutInflater;
+    private LinearLayout mReminderAdder;
+
+    // TODO This can be removed when the contacts content provider doesn't return duplicates
+    private int mUpdateCounts;
+    private static class ViewHolder {
+        QuickContactBadge badge;
+        ImageView presence;
+        int updateCounts;
+    }
+    private HashMap<String, ViewHolder> mViewHolders = new HashMap<String, ViewHolder>();
+    private PresenceQueryHandler mPresenceQueryHandler;
+
+    private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI;
+
+    int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0;
+    int PRESENCE_PROJECTION_PRESENCE_INDEX = 1;
+    int PRESENCE_PROJECTION_EMAIL_INDEX = 2;
+    int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3;
+
+    private static final String[] PRESENCE_PROJECTION = new String[] {
+        Email.CONTACT_ID,           // 0
+        Email.CONTACT_PRESENCE,     // 1
+        Email.DATA,                 // 2
+        Email.PHOTO_ID,             // 3
+    };
+
+    ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
+    ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
+    ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
+    ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
+    private int mColor;
+
+    private QueryHandler mHandler;
+
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            updateEvent(mView);
+        }
+    };
+
+    private static final int DIALOG_WIDTH = 500; // FRAG_TODO scale
+    private static final int DIALOG_HEIGHT = 500;
+    private boolean mIsDialog = false;
+    private int mX = -1;
+    private int mY = -1;
+
+    private class QueryHandler extends AsyncQueryService {
+        public QueryHandler(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            // if the activity is finishing, then close the cursor and return
+            final Activity activity = getActivity();
+            if (activity == null || activity.isFinishing()) {
+                cursor.close();
+                return;
+            }
+
+            switch (token) {
+            case TOKEN_QUERY_EVENT:
+                mEventCursor = Utils.matrixCursorFromCursor(cursor);
+                if (initEventCursor()) {
+                    // The cursor is empty. This can happen if the event was
+                    // deleted.
+                    // FRAG_TODO we should no longer rely on Activity.finish()
+                    activity.finish();
+                    return;
+                }
+                updateEvent(mView);
+
+                // start calendar query
+                Uri uri = Calendars.CONTENT_URI;
+                String[] args = new String[] {
+                        Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
+                startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
+                        CALENDARS_WHERE, args, null);
+                break;
+            case TOKEN_QUERY_CALENDARS:
+                mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
+                updateCalendar(mView);
+                // FRAG_TODO fragments shouldn't set the title anymore
+                updateTitle();
+                // update the action bar since our option set might have changed
+                activity.invalidateOptionsMenu();
+
+                // this is used for both attendees and reminders
+                args = new String[] { Long.toString(mEventId) };
+
+                // start attendees query
+                uri = Attendees.CONTENT_URI;
+                startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
+                        ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
+
+                // start reminders query
+                mOriginalHasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
+                if (mOriginalHasAlarm) {
+                    uri = Reminders.CONTENT_URI;
+                    startQuery(TOKEN_QUERY_REMINDERS, null, uri, REMINDERS_PROJECTION,
+                            REMINDERS_WHERE, args, REMINDERS_SORT);
+                } else {
+                    // if no reminders, hide the appropriate fields
+                    updateRemindersVisibility();
+                }
+                break;
+            case TOKEN_QUERY_ATTENDEES:
+                mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
+                initAttendeesCursor(mView);
+                updateResponse(mView);
+                break;
+            case TOKEN_QUERY_REMINDERS:
+                MatrixCursor reminderCursor = Utils.matrixCursorFromCursor(cursor);
+                try {
+                    // First pass: collect all the custom reminder minutes
+                    // (e.g., a reminder of 8 minutes) into a global list.
+                    while (reminderCursor.moveToNext()) {
+                        int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
+                        EventViewUtils.addMinutesToList(
+                                activity, mReminderValues, mReminderLabels, minutes);
+                    }
+
+                    // Second pass: create the reminder spinners
+                    reminderCursor.moveToPosition(-1);
+                    while (reminderCursor.moveToNext()) {
+                        int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
+                        mOriginalMinutes.add(minutes);
+                        EventViewUtils.addReminder(activity, mRemindersContainer,
+                                EventInfoFragment.this, mReminderItems, mReminderValues,
+                                mReminderLabels, minutes);
+                    }
+                } finally {
+                    updateRemindersVisibility();
+                    reminderCursor.close();
+                }
+                break;
+            case TOKEN_QUERY_DUPLICATE_CALENDARS:
+                mIsDuplicateName = cursor.getCount() > 1;
+                String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+//CLEANUP                String ownerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+//                if (mIsDuplicateName && !calendarName.equalsIgnoreCase(ownerAccount)) {
+//                    Resources res = activity.getResources();
+//                    TextView ownerText = (TextView) mView.findViewById(R.id.owner);
+//                    ownerText.setText(ownerAccount);
+//                    ownerText.setTextColor(res.getColor(R.color.calendar_owner_text_color));
+//                } else {
+//                    setVisibilityCommon(mView, R.id.owner, View.GONE);
+//                }
+                setTextCommon(mView, R.id.calendar, calendarName);
+                break;
+            }
+            cursor.close();
+        }
+
+    }
+
+    public EventInfoFragment() {
+        mUri = null;
+    }
+
+    public EventInfoFragment(Uri uri, long startMillis, long endMillis, int attendeeResponse) {
+        setStyle(DialogFragment.STYLE_NO_TITLE, 0);
+        mUri = uri;
+        mStartMillis = startMillis;
+        mEndMillis = endMillis;
+        mAttendeeResponseFromIntent = attendeeResponse;
+    }
+
+    public EventInfoFragment(long eventId, long startMillis, long endMillis) {
+        this(ContentUris.withAppendedId(Events.CONTENT_URI, eventId),
+                startMillis, endMillis, EventInfoActivity.ATTENDEE_NO_RESPONSE);
+        mEventId = eventId;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
+        }
+
+        if (mIsDialog) {
+            applyDialogParams();
+        }
+    }
+
+    private void applyDialogParams() {
+        Dialog dialog = getDialog();
+        dialog.setCanceledOnTouchOutside(true);
+
+        Window window = dialog.getWindow();
+        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        WindowManager.LayoutParams a = window.getAttributes();
+        a.dimAmount = .4f;
+
+        a.width = DIALOG_WIDTH;
+
+        if (mX != -1 || mY != -1) {
+            a.x = mX - a.width - 64; // FRAG_TODO event sender should return the left edge or a rect
+            a.y = mY - 64; // FRAG_TODO should set height after layout is done
+            a.gravity = Gravity.LEFT | Gravity.TOP;
+        }
+
+        window.setAttributes(a);
+    }
+
+    public void setDialogParams(int x, int y) {
+        mIsDialog = true;
+        mX = x;
+        mY = y;
+    }
+
+    // This is called when one of the "remove reminder" buttons is selected.
+    public void onClick(View v) {
+        LinearLayout reminderItem = (LinearLayout) v.getParent();
+        LinearLayout parent = (LinearLayout) reminderItem.getParent();
+        parent.removeView(reminderItem);
+        mReminderItems.remove(reminderItem);
+        updateRemindersVisibility();
+    }
+
+    public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
+        // If they selected the "No response" option, then don't display the
+        // dialog asking which events to change.
+        if (id == 0 && mResponseOffset == 0) {
+            return;
+        }
+
+        // If this is not a repeating event, then don't display the dialog
+        // asking which events to change.
+        if (!mIsRepeating) {
+            return;
+        }
+
+        // If the selection is the same as the original, then don't display the
+        // dialog asking which events to change.
+        int index = findResponseIndexFor(mOriginalAttendeeResponse);
+        if (position == index + mResponseOffset) {
+            return;
+        }
+
+        // This is a repeating event. We need to ask the user if they mean to
+        // change just this one instance or all instances.
+        mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
+    }
+
+    public void onNothingSelected(AdapterView<?> parent) {
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mEditResponseHelper = new EditResponseHelper(activity);
+        setHasOptionsMenu(true);
+        mHandler = new QueryHandler(activity);
+        mPresenceQueryHandler = new PresenceQueryHandler(activity, activity.getContentResolver());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mLayoutInflater = inflater;
+        mView = inflater.inflate(R.layout.event_info_activity, null);
+        mRemindersContainer = (LinearLayout) mView.findViewById(R.id.reminders_container);
+        mOrganizerContainer = (LinearLayout) mView.findViewById(R.id.organizer_container);
+        mOrganizerView = (TextView) mView.findViewById(R.id.organizer);
+
+        // Initialize the reminder values array.
+        Resources r = getActivity().getResources();
+        String[] strings = r.getStringArray(R.array.reminder_minutes_values);
+        int size = strings.length;
+        ArrayList<Integer> list = new ArrayList<Integer>(size);
+        for (int i = 0 ; i < size ; i++) {
+            list.add(Integer.parseInt(strings[i]));
+        }
+        mReminderValues = list;
+        String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
+        mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
+
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(getActivity());
+        String durationString =
+                prefs.getString(GeneralPreferences.KEY_DEFAULT_REMINDER, "0");
+        mDefaultReminderMinutes = Integer.parseInt(durationString);
+
+        // Setup the + Add Reminder Button
+        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
+            public void onClick(View v) {
+                addReminder();
+            }
+        };
+        ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add);
+        reminderAddButton.setOnClickListener(addReminderOnClickListener);
+
+//CLEANUP        mReminderAdder = (LinearLayout) mView.findViewById(R.id.reminder_adder);
+
+        if (mUri == null) {
+            // restore event ID from bundle
+            mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
+            mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
+            mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
+            mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
+        }
+
+        // start loading the data
+        mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
+                null, null, null);
+
+        Button b = (Button) mView.findViewById(R.id.done);
+        b.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                EventInfoFragment.this.dismiss();
+            }});
+
+        return mView;
+    }
+
+    private void updateTitle() {
+        Resources res = getActivity().getResources();
+        if (mCanModifyCalendar && !mIsOrganizer) {
+            getActivity().setTitle(res.getString(R.string.event_info_title_invite));
+        } else {
+            getActivity().setTitle(res.getString(R.string.event_info_title));
+        }
+    }
+
+    /**
+     * Initializes the event cursor, which is expected to point to the first
+     * (and only) result from a query.
+     * @return true if the cursor is empty.
+     */
+    private boolean initEventCursor() {
+        if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
+            return true;
+        }
+        mEventCursor.moveToFirst();
+        mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
+        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
+        mIsRepeating = (rRule != null);
+        return false;
+    }
+
+    private static class Attendee {
+        String mName;
+        String mEmail;
+
+        Attendee(String name, String email) {
+            mName = name;
+            mEmail = email;
+        }
+
+        String getDisplayName() {
+            if (TextUtils.isEmpty(mName)) {
+                return mEmail;
+            } else {
+                return mName;
+            }
+        }
+    }
+
+    @SuppressWarnings("fallthrough")
+    private void initAttendeesCursor(View view) {
+        mOriginalAttendeeResponse = EditEventHelper.ATTENDEE_NO_RESPONSE;
+        mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
+        mNumOfAttendees = 0;
+        if (mAttendeesCursor != null) {
+            mNumOfAttendees = mAttendeesCursor.getCount();
+            if (mAttendeesCursor.moveToFirst()) {
+                mAcceptedAttendees.clear();
+                mDeclinedAttendees.clear();
+                mTentativeAttendees.clear();
+                mNoResponseAttendees.clear();
+
+                do {
+                    int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
+                    String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
+                    String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
+
+                    if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) ==
+                            Attendees.RELATIONSHIP_ORGANIZER) {
+                        // Overwrites the one from Event table if available
+                        if (name != null && name.length() > 0) {
+                            mOrganizer = name;
+                        } else if (email != null && email.length() > 0) {
+                            mOrganizer = email;
+                        }
+                    }
+
+                    if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
+                            mCalendarOwnerAccount.equalsIgnoreCase(email)) {
+                        mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
+                        mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
+                    } else {
+                        // Don't show your own status in the list because:
+                        //  1) it doesn't make sense for event without other guests.
+                        //  2) there's a spinner for that for events with guests.
+                        switch(status) {
+                            case Attendees.ATTENDEE_STATUS_ACCEPTED:
+                                mAcceptedAttendees.add(new Attendee(name, email));
+                                break;
+                            case Attendees.ATTENDEE_STATUS_DECLINED:
+                                mDeclinedAttendees.add(new Attendee(name, email));
+                                break;
+                            case Attendees.ATTENDEE_STATUS_TENTATIVE:
+                                mTentativeAttendees.add(new Attendee(name, email));
+                                break;
+                            default:
+                                mNoResponseAttendees.add(new Attendee(name, email));
+                        }
+                    }
+                } while (mAttendeesCursor.moveToNext());
+                mAttendeesCursor.moveToFirst();
+
+                updateAttendees(view);
+            }
+        }
+        // only show the organizer if we're not the organizer and if
+        // we have attendee data (might have been removed by the server
+        // for events with a lot of attendees).
+//CLEANUP        if (!mIsOrganizer && mHasAttendeeData) {
+//            mOrganizerContainer.setVisibility(View.VISIBLE);
+//            mOrganizerView.setText(mOrganizer);
+//        } else {
+//            mOrganizerContainer.setVisibility(View.GONE);
+//        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId);
+        outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis);
+        outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis);
+
+        outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog);
+    }
+
+
+    @Override
+    public void onDestroyView() {
+        ArrayList<Integer> reminderMinutes = EventViewUtils.reminderItemsToMinutes(mReminderItems,
+                mReminderValues);
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
+        boolean changed = EditEventHelper.saveReminders(ops, mEventId, reminderMinutes,
+                mOriginalMinutes, false /* no force save */);
+        mHandler.startBatch(mHandler.getNextToken(), null,
+                Calendars.CONTENT_URI.getAuthority(), ops, Utils.UNDO_DELAY);
+
+        // Update the "hasAlarm" field for the event
+        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
+        int len = reminderMinutes.size();
+        boolean hasAlarm = len > 0;
+        if (hasAlarm != mOriginalHasAlarm) {
+            ContentValues values = new ContentValues();
+            values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
+            mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
+                    null, null, Utils.UNDO_DELAY);
+        }
+
+        changed |= saveResponse();
+        if (changed) {
+            Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show();
+        }
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mEventCursor != null) {
+            mEventCursor.close();
+        }
+        if (mCalendarsCursor != null) {
+            mCalendarsCursor.close();
+        }
+        if (mAttendeesCursor != null) {
+            mAttendeesCursor.close();
+        }
+        super.onDestroy();
+    }
+
+    private boolean canAddReminders() {
+        return !mIsBusyFreeCalendar && mReminderItems.size() < MAX_REMINDERS;
+    }
+
+    private void addReminder() {
+        // TODO: when adding a new reminder, make it different from the
+        // last one in the list (if any).
+        if (mDefaultReminderMinutes == 0) {
+            EventViewUtils.addReminder(getActivity(), mRemindersContainer, this, mReminderItems,
+                    mReminderValues, mReminderLabels, 10 /* minutes */);
+        } else {
+            EventViewUtils.addReminder(getActivity(), mRemindersContainer, this, mReminderItems,
+                    mReminderValues, mReminderLabels, mDefaultReminderMinutes);
+        }
+        updateRemindersVisibility();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        MenuItem item;
+        item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
+                R.string.add_new_reminder);
+        item.setIcon(R.drawable.ic_menu_reminder);
+        item.setAlphabeticShortcut('r');
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+        item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label);
+        item.setIcon(android.R.drawable.ic_menu_edit);
+        item.setAlphabeticShortcut('e');
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+        item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label);
+        item.setIcon(android.R.drawable.ic_menu_delete);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        boolean canAddReminders = canAddReminders();
+        menu.setGroupVisible(MENU_GROUP_REMINDER, canAddReminders);
+        menu.setGroupEnabled(MENU_GROUP_REMINDER, canAddReminders);
+
+        menu.setGroupVisible(MENU_GROUP_EDIT, mCanModifyEvent);
+        menu.setGroupEnabled(MENU_GROUP_EDIT, mCanModifyEvent);
+        menu.setGroupVisible(MENU_GROUP_DELETE, mCanModifyCalendar);
+        menu.setGroupEnabled(MENU_GROUP_DELETE, mCanModifyCalendar);
+
+        super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (item.getItemId()) {
+            case MENU_ADD_REMINDER:
+                addReminder();
+                break;
+            case MENU_EDIT:
+                doEdit();
+                break;
+            case MENU_DELETE:
+                doDelete();
+                break;
+        }
+        return true;
+    }
+
+//CLEANUP    @Override
+//    public boolean onKeyDown(int keyCode, KeyEvent event) {
+//        if (keyCode == KeyEvent.KEYCODE_DEL) {
+//            doDelete();
+//            return true;
+//        }
+//        return super.onKeyDown(keyCode, event);
+//    }
+
+    private void updateRemindersVisibility() {
+//CLEANUP        if (mIsBusyFreeCalendar) {
+//            mRemindersContainer.setVisibility(View.GONE);
+//        } else {
+//            mRemindersContainer.setVisibility(View.VISIBLE);
+//            mReminderAdder.setVisibility(canAddReminders() ? View.VISIBLE : View.GONE);
+//        }
+    }
+
+    /**
+     * Asynchronously saves the response to an invitation if the user changed
+     * the response. Returns true if the database will be updated.
+     *
+     * @param cr the ContentResolver
+     * @return true if the database will be changed
+     */
+    private boolean saveResponse() {
+        if (mAttendeesCursor == null || mEventCursor == null) {
+            return false;
+        }
+        Spinner spinner = (Spinner) getView().findViewById(R.id.response_value);
+        int position = spinner.getSelectedItemPosition() - mResponseOffset;
+        if (position <= 0) {
+            return false;
+        }
+
+        int status = EditEventHelper.ATTENDEE_VALUES[position];
+
+        // If the status has not changed, then don't update the database
+        if (status == mOriginalAttendeeResponse) {
+            return false;
+        }
+
+        // If we never got an owner attendee id we can't set the status
+        if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
+            return false;
+        }
+
+        if (!mIsRepeating) {
+            // This is a non-repeating event
+            updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
+            return true;
+        }
+
+        // This is a repeating event
+        int whichEvents = mEditResponseHelper.getWhichEvents();
+        switch (whichEvents) {
+            case -1:
+                return false;
+            case UPDATE_SINGLE:
+                createExceptionResponse(mEventId, mCalendarOwnerAttendeeId, status);
+                return true;
+            case UPDATE_ALL:
+                updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
+                return true;
+            default:
+                Log.e(TAG, "Unexpected choice for updating invitation response");
+                break;
+        }
+        return false;
+    }
+
+    private void updateResponse(long eventId, long attendeeId, int status) {
+        // Update the attendee status in the attendees table.  the provider
+        // takes care of updating the self attendance status.
+        ContentValues values = new ContentValues();
+
+        if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
+            values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
+        }
+        values.put(Attendees.ATTENDEE_STATUS, status);
+        values.put(Attendees.EVENT_ID, eventId);
+
+        Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
+
+        mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
+                null, null, Utils.UNDO_DELAY);
+    }
+
+    private void createExceptionResponse(long eventId, long attendeeId,
+            int status) {
+        if (mEventCursor == null || !mEventCursor.moveToFirst()) {
+            return;
+        }
+
+        ContentValues values = new ContentValues();
+
+        String title = mEventCursor.getString(EVENT_INDEX_TITLE);
+        String timezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
+        int calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
+        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+        String syncId = mEventCursor.getString(EVENT_INDEX_SYNC_ID);
+
+        values.put(Events.TITLE, title);
+        values.put(Events.EVENT_TIMEZONE, timezone);
+        values.put(Events.ALL_DAY, allDay ? 1 : 0);
+        values.put(Events.CALENDAR_ID, calendarId);
+        values.put(Events.DTSTART, mStartMillis);
+        values.put(Events.DTEND, mEndMillis);
+        values.put(Events.ORIGINAL_EVENT, syncId);
+        values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
+        values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
+        values.put(Events.STATUS, Events.STATUS_CONFIRMED);
+        values.put(Events.SELF_ATTENDEE_STATUS, status);
+
+        // Create a recurrence exception
+        mHandler.startInsert(mHandler.getNextToken(), null,
+                Events.CONTENT_URI, values, Utils.UNDO_DELAY);
+    }
+
+    private int findResponseIndexFor(int response) {
+        int size = EditEventHelper.ATTENDEE_VALUES.length;
+        for (int index = 0; index < size; index++) {
+            if (EditEventHelper.ATTENDEE_VALUES[index] == response) {
+                return index;
+            }
+        }
+        return 0;
+    }
+
+    private void doEdit() {
+        CalendarController.getInstance(getActivity()).sendEventRelatedEvent(
+                this, EventType.EDIT_EVENT, mEventId, mStartMillis, mEndMillis, 0, 0);
+    }
+
+    private void doDelete() {
+        CalendarController.getInstance(getActivity()).sendEventRelatedEvent(
+                this, EventType.DELETE_EVENT, mEventId, mStartMillis, mEndMillis, 0, 0);
+    }
+
+    private void updateEvent(View view) {
+        if (mEventCursor == null) {
+            return;
+        }
+
+        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
+        if (eventName == null || eventName.length() == 0) {
+            eventName = getActivity().getString(R.string.no_title_label);
+        }
+
+        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+        String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
+        String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
+        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
+        boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
+        String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
+        mColor = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
+
+        view.findViewById(R.id.color).setBackgroundColor(mColor);
+
+        TextView title = (TextView) view.findViewById(R.id.title);
+        title.setTextColor(mColor);
+
+//        View divider = view.findViewById(R.id.divider);
+//        divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+
+        // What
+        if (eventName != null) {
+            setTextCommon(view, R.id.title, eventName);
+        }
+
+        // When
+        String when;
+        int flags;
+        if (allDay) {
+            flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY
+            | DateUtils.FORMAT_SHOW_DATE;
+        } else {
+            flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
+            if (DateFormat.is24HourFormat(getActivity())) {
+                flags |= DateUtils.FORMAT_24HOUR;
+            }
+        }
+        when = Utils.formatDateRange(getActivity(), mStartMillis, mEndMillis, flags);
+        setTextCommon(view, R.id.when, when);
+
+//CLEANUP        // Show the event timezone if it is different from the local timezone
+//        Time time = new Time();
+//        String localTimezone = time.timezone;
+//        if (allDay) {
+//            localTimezone = Time.TIMEZONE_UTC;
+//        }
+//        if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) {
+//            String displayName;
+//            TimeZone tz = TimeZone.getTimeZone(localTimezone);
+//            if (tz == null || tz.getID().equals("GMT")) {
+//                displayName = localTimezone;
+//            } else {
+//                displayName = tz.getDisplayName();
+//            }
+//
+//            setTextCommon(view, R.id.timezone, displayName);
+//            setVisibilityCommon(view, R.id.timezone_container, View.VISIBLE);
+//        } else {
+//            setVisibilityCommon(view, R.id.timezone_container, View.GONE);
+//        }
+
+        // Repeat
+        if (rRule != null) {
+            EventRecurrence eventRecurrence = new EventRecurrence();
+            eventRecurrence.parse(rRule);
+            Time date = new Time(Utils.getTimeZone(getActivity(), mTZUpdater));
+            if (allDay) {
+                date.timezone = Time.TIMEZONE_UTC;
+            }
+            date.set(mStartMillis);
+            eventRecurrence.setStartDate(date);
+            String repeatString = EventRecurrenceFormatter.getRepeatString(
+                    getActivity().getResources(), eventRecurrence);
+            setTextCommon(view, R.id.repeat, repeatString);
+            setVisibilityCommon(view, R.id.repeat_container, View.VISIBLE);
+        } else {
+            setVisibilityCommon(view, R.id.repeat_container, View.GONE);
+        }
+
+        // Where
+        if (location == null || location.length() == 0) {
+            setVisibilityCommon(view, R.id.where, View.GONE);
+        } else {
+            final TextView textView = (TextView) view.findViewById(R.id.where);
+            if (textView != null) {
+                    textView.setAutoLinkMask(0);
+                    textView.setText(location);
+                    Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q=");
+                    textView.setOnTouchListener(new OnTouchListener() {
+                        public boolean onTouch(View v, MotionEvent event) {
+                            try {
+                                return v.onTouchEvent(event);
+                            } catch (ActivityNotFoundException e) {
+                                // ignore
+                                return true;
+                            }
+                        }
+                    });
+            }
+        }
+
+        // Description
+        if (description == null || description.length() == 0) {
+            setVisibilityCommon(view, R.id.description, View.GONE);
+        } else {
+            setTextCommon(view, R.id.description, description);
+        }
+    }
+
+    private void updateCalendar(View view) {
+        mCalendarOwnerAccount = "";
+        if (mCalendarsCursor != null && mEventCursor != null) {
+            mCalendarsCursor.moveToFirst();
+            String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+            mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
+            mOrganizerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
+
+            String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+
+            // start duplicate calendars query
+            mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, Calendars.CONTENT_URI,
+                    CALENDARS_PROJECTION, CALENDARS_DUPLICATE_NAME_WHERE,
+                    new String[] {displayName}, null);
+
+            String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
+            mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(eventOrganizer);
+            mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
+            mOrganizer = eventOrganizer;
+            mCanModifyCalendar =
+                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS;
+            mIsBusyFreeCalendar =
+                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS;
+            mCanModifyEvent = mCanModifyCalendar
+                    && (mIsOrganizer || (mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0));
+            if (mCanModifyEvent) {
+                Button b = (Button) mView.findViewById(R.id.edit);
+                b.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        doEdit();
+                        EventInfoFragment.this.dismiss();
+                    }});
+                b.setVisibility(View.VISIBLE);
+            }
+        } else {
+//CLEANUP            setVisibilityCommon(view, R.id.calendar_container, View.GONE);
+        }
+    }
+
+    private void updateAttendees(View view) {
+        TextView tv = (TextView) view.findViewById(R.id.attendee_list);
+        SpannableStringBuilder sb = new SpannableStringBuilder();
+        formatAttendees(mAcceptedAttendees, sb, Attendees.ATTENDEE_STATUS_ACCEPTED);
+        formatAttendees(mDeclinedAttendees, sb, Attendees.ATTENDEE_STATUS_DECLINED);
+        formatAttendees(mTentativeAttendees, sb, Attendees.ATTENDEE_STATUS_TENTATIVE);
+        formatAttendees(mNoResponseAttendees, sb, Attendees.ATTENDEE_STATUS_NONE);
+        tv.setText(sb);
+
+//CLEANUP        LinearLayout attendeesLayout = (LinearLayout) view.findViewById(R.id.attendee_list);
+//        attendeesLayout.removeAllViewsInLayout();
+//        ++mUpdateCounts;
+//        if(mAcceptedAttendees.size() == 0 && mDeclinedAttendees.size() == 0 &&
+//                mTentativeAttendees.size() == mNoResponseAttendees.size()) {
+//            // If all guests have no response just list them as guests,
+//            CharSequence guestsLabel =
+//                getActivity().getResources().getText(R.string.attendees_label);
+//            addAttendeesToLayout(mNoResponseAttendees, attendeesLayout, guestsLabel);
+//        } else {
+//            // If we have any responses then divide them up by response
+//            CharSequence[] entries;
+//            entries = getActivity().getResources().getTextArray(R.array.response_labels2);
+//            addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]);
+//            addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]);
+//            addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]);
+//        }
+    }
+
+    private void formatAttendees(ArrayList<Attendee> attendees, SpannableStringBuilder sb, int type) {
+        if (attendees.size() <= 0) {
+            return;
+        }
+
+        int begin = sb.length();
+        boolean firstTime = sb.length() == 0;
+
+        if (firstTime == false) {
+            begin += 2; // skip over the ", " for formatting.
+        }
+
+        for (Attendee attendee : attendees) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(", ");
+            }
+
+            String name = attendee.getDisplayName();
+            sb.append(name);
+        }
+
+        switch (type) {
+            case Attendees.ATTENDEE_STATUS_ACCEPTED:
+                break;
+            case Attendees.ATTENDEE_STATUS_DECLINED:
+                sb.setSpan(new StrikethroughSpan(), begin, sb.length(),
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                // fall through
+            default:
+                // The last INCLUSIVE causes the foreground color to be applied
+                // to the rest of the span. If not, the comma at the end of the
+                // declined or tentative may be black.
+                sb.setSpan(new ForegroundColorSpan(0xFF888888), begin, sb.length(),
+                        Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+                break;
+        }
+    }
+
+    private void addAttendeesToLayout(ArrayList<Attendee> attendees, LinearLayout attendeeList,
+            CharSequence sectionTitle) {
+        if (attendees.size() == 0) {
+            return;
+        }
+
+        // Yes/No/Maybe Title
+        View titleView = mLayoutInflater.inflate(R.layout.contact_item, null);
+        titleView.findViewById(R.id.badge).setVisibility(View.GONE);
+        View divider = titleView.findViewById(R.id.separator);
+        divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+
+        TextView title = (TextView) titleView.findViewById(R.id.name);
+        title.setText(getActivity().getString(R.string.response_label, sectionTitle,
+                attendees.size()));
+        title.setTextAppearance(getActivity(), R.style.TextAppearance_EventInfo_Label);
+        attendeeList.addView(titleView);
+
+        // Attendees
+        int numOfAttendees = attendees.size();
+        StringBuilder selection = new StringBuilder(Email.DATA + " IN (");
+        String[] selectionArgs = new String[numOfAttendees];
+
+        for (int i = 0; i < numOfAttendees; ++i) {
+            Attendee attendee = attendees.get(i);
+            selectionArgs[i] = attendee.mEmail;
+
+            View v = mLayoutInflater.inflate(R.layout.contact_item, null);
+            v.setTag(attendee);
+
+            View separator = v.findViewById(R.id.separator);
+            separator.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+
+            // Text
+            TextView tv = (TextView) v.findViewById(R.id.name);
+            String name = attendee.mName;
+            if (name == null || name.length() == 0) {
+                name = attendee.mEmail;
+            }
+            tv.setText(name);
+
+            ViewHolder vh = new ViewHolder();
+            vh.badge = (QuickContactBadge) v.findViewById(R.id.badge);
+            vh.badge.assignContactFromEmail(attendee.mEmail, true);
+            vh.presence = (ImageView) v.findViewById(R.id.presence);
+            mViewHolders.put(attendee.mEmail, vh);
+
+            if (i == 0) {
+                selection.append('?');
+            } else {
+                selection.append(", ?");
+            }
+
+            attendeeList.addView(v);
+        }
+        selection.append(')');
+
+        mPresenceQueryHandler.startQuery(mUpdateCounts, attendees, CONTACT_DATA_WITH_PRESENCE_URI,
+                PRESENCE_PROJECTION, selection.toString(), selectionArgs, null);
+    }
+
+    private class PresenceQueryHandler extends AsyncQueryHandler {
+        Context mContext;
+
+        public PresenceQueryHandler(Context context, ContentResolver cr) {
+            super(cr);
+            mContext = context;
+        }
+
+        @Override
+        protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) {
+            if (cursor == null) {
+                if (DEBUG) {
+                    Log.e(TAG, "onQueryComplete: cursor == null");
+                }
+                return;
+            }
+
+            try {
+                cursor.moveToPosition(-1);
+                while (cursor.moveToNext()) {
+                    String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX);
+                    int contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX);
+                    ViewHolder vh = mViewHolders.get(email);
+                    int photoId = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX);
+                    if (DEBUG) {
+                        Log.e(TAG, "onQueryComplete Id: " + contactId + " PhotoId: " + photoId
+                                + " Email: " + email);
+                    }
+                    if (vh == null) {
+                        continue;
+                    }
+                    ImageView presenceView = vh.presence;
+                    if (presenceView != null) {
+                        int status = cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX);
+                        presenceView.setImageResource(Presence.getPresenceIconResourceId(status));
+                        presenceView.setVisibility(View.VISIBLE);
+                    }
+
+                    if (photoId > 0 && vh.updateCounts < queryIndex) {
+                        vh.updateCounts = queryIndex;
+                        Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
+                                contactId);
+
+                        // TODO, modify to batch queries together
+                        ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(mContext,
+                                vh.badge, personUri, R.drawable.ic_contact_picture);
+                    }
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    void updateResponse(View view) {
+        // we only let the user accept/reject/etc. a meeting if:
+        // a) you can edit the event's containing calendar AND
+        // b) you're not the organizer and only attendee AND
+        // c) organizerCanRespond is enabled for the calendar
+        // (if the attendee data has been hidden, the visible number of attendees
+        // will be 1 -- the calendar owner's).
+        // (there are more cases involved to be 100% accurate, such as
+        // paying attention to whether or not an attendee status was
+        // included in the feed, but we're currently omitting those corner cases
+        // for simplicity).
+//CLEANUP
+        if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
+                (mIsOrganizer && !mOrganizerCanRespond)) {
+            setVisibilityCommon(view, R.id.response_container, View.GONE);
+            return;
+        }
+
+        setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
+
+        Spinner spinner = (Spinner) view.findViewById(R.id.response_value);
+
+        mResponseOffset = 0;
+
+        /* If the user has previously responded to this event
+         * we should not allow them to select no response again.
+         * Switch the entries to a set of entries without the
+         * no response option.
+         */
+        if ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED)
+                && (mOriginalAttendeeResponse != EditEventHelper.ATTENDEE_NO_RESPONSE)
+                && (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) {
+            CharSequence[] entries;
+            entries = getActivity().getResources().getTextArray(R.array.response_labels2);
+            mResponseOffset = -1;
+            ArrayAdapter<CharSequence> adapter =
+                new ArrayAdapter<CharSequence>(getActivity(),
+                        android.R.layout.simple_spinner_item, entries);
+            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            spinner.setAdapter(adapter);
+        }
+
+        int index;
+        if (mAttendeeResponseFromIntent != EditEventHelper.ATTENDEE_NO_RESPONSE) {
+            index = findResponseIndexFor(mAttendeeResponseFromIntent);
+        } else {
+            index = findResponseIndexFor(mOriginalAttendeeResponse);
+        }
+        spinner.setSelection(index + mResponseOffset);
+        spinner.setOnItemSelectedListener(this);
+    }
+
+    private void setTextCommon(View view, int id, CharSequence text) {
+        TextView textView = (TextView) view.findViewById(id);
+        if (textView == null)
+            return;
+        textView.setText(text);
+    }
+
+    private void setVisibilityCommon(View view, int id, int visibility) {
+        View v = view.findViewById(id);
+        if (v != null) {
+            v.setVisibility(visibility);
+        }
+        return;
+    }
+
+    /**
+     * Taken from com.google.android.gm.HtmlConversationActivity
+     *
+     * Send the intent that shows the Contact info corresponding to the email address.
+     */
+    public void showContactInfo(Attendee attendee, Rect rect) {
+        // First perform lookup query to find existing contact
+        final ContentResolver resolver = getActivity().getContentResolver();
+        final String address = attendee.mEmail;
+        final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
+                Uri.encode(address));
+        final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
+
+        if (lookupUri != null) {
+            // Found matching contact, trigger QuickContact
+            QuickContact.showQuickContact(getActivity(), rect, lookupUri,
+                    QuickContact.MODE_MEDIUM, null);
+        } else {
+            // No matching contact, ask user to create one
+            final Uri mailUri = Uri.fromParts("mailto", address, null);
+            final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
+
+            // Pass along full E-mail string for possible create dialog
+            Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
+            intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
+
+            // Only provide personal name hint if we have one
+            final String senderPersonal = attendee.mName;
+            if (!TextUtils.isEmpty(senderPersonal)) {
+                intent.putExtra(Intents.Insert.NAME, senderPersonal);
+            }
+
+            startActivity(intent);
+        }
+    }
+
+    @Override
+    public void eventsChanged() {
+    }
+
+    @Override
+    public boolean getAllDay() {
+        return false;
+    }
+
+    @Override
+    public long getSelectedTime() {
+        return mStartMillis;
+    }
+
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.EVENTS_CHANGED;
+    }
+
+    @Override
+    public void goTo(Time time, boolean animate) {
+    }
+
+    @Override
+    public void goToToday() {
+
+    }
+
+    @Override
+    public void handleEvent(EventInfo event) {
+        if (event.eventType == EventType.EVENTS_CHANGED) {
+            // reload the data
+            mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
+                    null, null, null);
+        }
+
+    }
+}
diff --git a/src/com/android/calendar/EventLoader.java b/src/com/android/calendar/EventLoader.java
index 57201f2..42e907c 100644
--- a/src/com/android/calendar/EventLoader.java
+++ b/src/com/android/calendar/EventLoader.java
@@ -226,7 +226,7 @@
      * created are used, and the most recent call's worth of data is loaded into events and posted
      * via the uiCallback.
      */
-    void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
+    public void loadEventsInBackground(final int numDays, final ArrayList<Event> events,
             long start, final Runnable successCallback, final Runnable cancelCallback) {
 
         // Increment the sequence number for requests.  We don't care if the
diff --git a/src/com/android/calendar/GeneralPreferences.java b/src/com/android/calendar/GeneralPreferences.java
new file mode 100644
index 0000000..4595986
--- /dev/null
+++ b/src/com/android/calendar/GeneralPreferences.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2007 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.calendar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.preference.RingtonePreference;
+import android.provider.Calendar.CalendarCache;
+import android.provider.SearchRecentSuggestions;
+import android.widget.Toast;
+
+public class GeneralPreferences extends PreferenceFragment implements
+        OnSharedPreferenceChangeListener, OnPreferenceChangeListener {
+    // The name of the shared preferences file. This name must be maintained for historical
+    // reasons, as it's what PreferenceManager assigned the first time the file was created.
+    private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
+
+    // Preference keys
+    public static final String KEY_HIDE_DECLINED = "preferences_hide_declined";
+    public static final String KEY_WEEK_START_DAY = "preferences_week_start_day";
+
+    public static final String KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history";
+
+    public static final String KEY_ALERTS = "preferences_alerts";
+    public static final String KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate";
+    public static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen";
+    public static final String KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone";
+    public static final String KEY_ALERTS_POPUP = "preferences_alerts_popup";
+
+    public static final String KEY_DEFAULT_REMINDER = "preferences_default_reminder";
+
+    /** Key to SharePreference for default view (CalendarController.ViewType) */
+    public static final String KEY_START_VIEW = "preferred_startView";
+    /**
+     *  Key to SharePreference for default detail view (CalendarController.ViewType)
+     *  Typically used by widget
+     */
+    public static final String KEY_DETAILED_VIEW = "preferred_detailedView";
+    public static final String KEY_DEFAULT_CALENDAR = "preference_defaultCalendar";
+
+    // These must be in sync with the array preferences_week_start_day_values
+    public static final String WEEK_START_DEFAULT = "-1";
+    public static final String WEEK_START_SATURDAY = "7";
+    public static final String WEEK_START_SUNDAY = "1";
+    public static final String WEEK_START_MONDAY = "2";
+
+    // These keys are kept to enable migrating users from previous versions
+    private static final String KEY_ALERTS_TYPE = "preferences_alerts_type";
+    private static final String ALERT_TYPE_ALERTS = "0";
+    private static final String ALERT_TYPE_STATUS_BAR = "1";
+    private static final String ALERT_TYPE_OFF = "2";
+    static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
+    static final String KEY_HOME_TZ = "preferences_home_tz";
+
+    // Default preference values
+    public static final int DEFAULT_START_VIEW = CalendarController.ViewType.WEEK;
+    public static final int DEFAULT_DETAILED_VIEW = CalendarController.ViewType.DAY;
+
+    CheckBoxPreference mAlert;
+    ListPreference mVibrateWhen;
+    RingtonePreference mRingtone;
+    CheckBoxPreference mPopup;
+    CheckBoxPreference mUseHomeTZ;
+    ListPreference mHomeTZ;
+
+    private static CharSequence[][] mTimezones;
+
+    /** Return a properly configured SharedPreferences instance */
+    public static SharedPreferences getSharedPreferences(Context context) {
+        return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+    }
+
+    /** Set the default shared preferences in the proper context */
+    public static void setDefaultValues(Context context) {
+        PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
+                R.xml.general_preferences, false);
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Activity activity = getActivity();
+
+        // Make sure to always use the same preferences file regardless of the package name
+        // we're running under
+        final PreferenceManager preferenceManager = getPreferenceManager();
+        final SharedPreferences sharedPreferences = getSharedPreferences(activity);
+        preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.general_preferences);
+
+        PreferenceScreen preferenceScreen = getPreferenceScreen();
+        preferenceScreen.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+        mAlert = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS);
+        mVibrateWhen = (ListPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE_WHEN);
+        mRingtone = (RingtonePreference) preferenceScreen.findPreference(KEY_ALERTS_RINGTONE);
+        mPopup = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_POPUP);
+        mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED);
+        mUseHomeTZ.setOnPreferenceChangeListener(this);
+        mHomeTZ = (ListPreference) preferenceScreen.findPreference(KEY_HOME_TZ);
+        String tz = mHomeTZ.getValue();
+
+        if (mTimezones == null) {
+            mTimezones = (new TimezoneAdapter(getActivity(), tz)).getAllTimezones();
+        }
+        mHomeTZ.setEntryValues(mTimezones[0]);
+        mHomeTZ.setEntries(mTimezones[1]);
+        mHomeTZ.setSummary(mHomeTZ.getEntry());
+        mHomeTZ.setOnPreferenceChangeListener(this);
+//        mHomeTZ = (ListPreference) preferenceScreen.findPreference(KEY_HOME_TZ);
+//        mHomeTZ.setSummary(mHomeTZ.getEntry());
+//        mHomeTZ.setOnPreferenceChangeListener(this);
+
+        migrateOldPreferences(sharedPreferences);
+
+        updateChildPreferences();
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (key.equals(KEY_ALERTS)) {
+            updateChildPreferences();
+        }
+    }
+
+    /**
+     * Handles time zone preference changes
+     */
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        String tz;
+        if (preference == mUseHomeTZ) {
+            if ((Boolean)newValue) {
+                tz = mHomeTZ.getValue();
+            } else {
+                tz = CalendarCache.TIMEZONE_TYPE_AUTO;
+            }
+        } else if (preference == mHomeTZ) {
+            tz = (String) newValue;
+            // We set the value here so we can read back the entry
+            mHomeTZ.setValue(tz);
+            mHomeTZ.setSummary(mHomeTZ.getEntry());
+        } else {
+            return false;
+        }
+        Utils.setTimeZone(getActivity(), tz);
+        return true;
+    }
+
+    /**
+     * If necessary, upgrades previous versions of preferences to the current
+     * set of keys and values.
+     * @param prefs the preferences to upgrade
+     */
+    private void migrateOldPreferences(SharedPreferences prefs) {
+        // If needed, migrate vibration setting from a previous version
+        if (!prefs.contains(KEY_ALERTS_VIBRATE_WHEN) &&
+                prefs.contains(KEY_ALERTS_VIBRATE)) {
+            int stringId = prefs.getBoolean(KEY_ALERTS_VIBRATE, false) ?
+                    R.string.prefDefault_alerts_vibrate_true :
+                    R.string.prefDefault_alerts_vibrate_false;
+            mVibrateWhen.setValue(getActivity().getString(stringId));
+        }
+        // If needed, migrate the old alerts type settin
+        if (!prefs.contains(KEY_ALERTS) && prefs.contains(KEY_ALERTS_TYPE)) {
+            String type = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR);
+            if (type.equals(ALERT_TYPE_OFF)) {
+                mAlert.setChecked(false);
+                mPopup.setChecked(false);
+                mPopup.setEnabled(false);
+            } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
+                mAlert.setChecked(true);
+                mPopup.setChecked(false);
+                mPopup.setEnabled(true);
+            } else if (type.equals(ALERT_TYPE_ALERTS)) {
+                mAlert.setChecked(true);
+                mPopup.setChecked(true);
+                mPopup.setEnabled(true);
+            }
+            // clear out the old setting
+            prefs.edit().remove(KEY_ALERTS_TYPE).commit();
+        }
+    }
+
+    /**
+     * Keeps the dependent settings in sync with the parent preference, so for
+     * example, when notifications are turned off, we disable the preferences
+     * for configuring the exact notification behavior.
+     */
+    private void updateChildPreferences() {
+        if (mAlert.isChecked()) {
+            mVibrateWhen.setEnabled(true);
+            mRingtone.setEnabled(true);
+            mPopup.setEnabled(true);
+        } else {
+            mVibrateWhen.setValue(
+                    getActivity().getString(R.string.prefDefault_alerts_vibrate_false));
+            mVibrateWhen.setEnabled(false);
+            mRingtone.setEnabled(false);
+            mPopup.setEnabled(false);
+        }
+    }
+
+
+    @Override @SuppressWarnings("deprecation")
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        String key = preference.getKey();
+        if (key.equals(KEY_CLEAR_SEARCH_HISTORY)) {
+            SearchRecentSuggestions suggestions = new SearchRecentSuggestions(getActivity(),
+                    CalendarRecentSuggestionsProvider.AUTHORITY,
+                    CalendarRecentSuggestionsProvider.MODE);
+            suggestions.clearHistory();
+            Toast.makeText(getActivity(), R.string.search_history_cleared,
+                    Toast.LENGTH_SHORT).show();
+            return true;
+        }
+        return false;
+    }
+
+
+}
diff --git a/src/com/android/calendar/LaunchActivity.java b/src/com/android/calendar/LaunchActivity.java
deleted file mode 100644
index 5dcb4f4..0000000
--- a/src/com/android/calendar/LaunchActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Calendar;
-import android.provider.Settings;
-
-public class LaunchActivity extends Activity {
-    private static final String TAG = "LaunchActivity";
-
-    static final String KEY_DETAIL_VIEW = "DETAIL_VIEW";
-    static final String KEY_VIEW_TYPE = "VIEW";
-    static final String VIEW_TYPE_DAY = "DAY";
-
-    private Bundle mExtras;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        mExtras = getIntent().getExtras();
-
-        // Our UI is not something intended for the user to see.  We just
-        // stick around until we can figure out what to do next based on
-        // the current state of the system.
-        // Removed because it causes draw problems when entering in landscape orientation
-        // TODO: Figure out draw problem. Original reason for removal due to b/2008662
-        // setVisible(false);
-
-        // Only try looking for an account if this is the first launch.
-        if (icicle == null) {
-            Account[] accounts = AccountManager.get(this).getAccounts();
-            if(accounts.length > 0) {
-                // If the only account is an account that can't use Calendar we let the user into
-                // Calendar, but they can't create any events until they add an account with a
-                // Calendar.
-                launchCalendarView();
-            } else {
-                // If we failed to find a valid Calendar, bounce the user to the account settings
-                // screen. Using the Calendar authority has the added benefit of only showing
-                // account types that use Calendar when you enter the add account screen from here.
-                final Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
-                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
-                    Calendar.AUTHORITY
-                });
-                startActivityForResult(intent, 0);
-            }
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Account[] accounts = AccountManager.get(this).getAccounts();
-        if(accounts.length > 0) {
-            // If the only account is an account that can't use Calendar we let the user into
-            // Calendar, but they can't create any events until they add an account with a
-            // Calendar.
-            launchCalendarView();
-        } else {
-            finish();
-        }
-    }
-
-    private void launchCalendarView() {
-        // Get the data for from this intent, if any
-        Intent myIntent = getIntent();
-        Uri myData = myIntent.getData();
-
-        // Set up the intent for the start activity
-        Intent intent = new Intent();
-        if (myData != null) {
-            intent.setData(myData);
-        }
-
-        String defaultViewKey = CalendarPreferenceActivity.KEY_START_VIEW;
-        if (mExtras != null) {
-            intent.putExtras(mExtras);
-            if (mExtras.getBoolean(KEY_DETAIL_VIEW, false)) {
-                defaultViewKey = CalendarPreferenceActivity.KEY_DETAILED_VIEW;
-            } else if (VIEW_TYPE_DAY.equals(mExtras.getString(KEY_VIEW_TYPE))) {
-                defaultViewKey = VIEW_TYPE_DAY;
-            }
-        }
-        intent.putExtras(myIntent);
-
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
-        String startActivity;
-        if (defaultViewKey.equals(VIEW_TYPE_DAY)) {
-            startActivity = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.DAY_VIEW_ID];
-        } else if (defaultViewKey.equals(CalendarPreferenceActivity.KEY_DETAILED_VIEW)) {
-            startActivity = prefs.getString(defaultViewKey,
-                    CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW);
-        } else {
-            startActivity = prefs.getString(defaultViewKey,
-                    CalendarPreferenceActivity.DEFAULT_START_VIEW);
-        }
-
-        intent.setClassName(this, startActivity);
-        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        startActivity(intent);
-        finish();
-    }
-}
diff --git a/src/com/android/calendar/MenuHelper.java b/src/com/android/calendar/MenuHelper.java
deleted file mode 100644
index 0b441e6..0000000
--- a/src/com/android/calendar/MenuHelper.java
+++ /dev/null
@@ -1,170 +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.calendar;
-
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import static android.provider.Calendar.EVENT_END_TIME;
-import android.app.Activity;
-import android.content.Intent;
-import android.text.format.DateUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-
-public class MenuHelper {
-    private static final int MENU_GROUP_AGENDA = 1;
-    private static final int MENU_GROUP_DAY = 2;
-    private static final int MENU_GROUP_WEEK = 3;
-    private static final int MENU_GROUP_MONTH = 4;
-    private static final int MENU_GROUP_EVENT_CREATE = 5;
-    private static final int MENU_GROUP_TODAY = 6;
-    private static final int MENU_GROUP_SELECT_CALENDARS = 7;
-    private static final int MENU_GROUP_PREFERENCES = 8;
-
-    public static final int MENU_GOTO_TODAY = 1;
-    public static final int MENU_AGENDA = 2;
-    public static final int MENU_DAY = 3;
-    public static final int MENU_WEEK = 4;
-    public static final int MENU_EVENT_VIEW = 5;
-    public static final int MENU_EVENT_CREATE = 6;
-    public static final int MENU_EVENT_EDIT = 7;
-    public static final int MENU_EVENT_DELETE = 8;
-    public static final int MENU_MONTH = 9;
-    public static final int MENU_SELECT_CALENDARS = 10;
-    public static final int MENU_PREFERENCES = 11;
-
-    public static void onPrepareOptionsMenu(Activity activity, Menu menu) {
-
-        if (activity instanceof AgendaActivity) {
-            menu.setGroupVisible(MENU_GROUP_AGENDA, true);
-            menu.setGroupEnabled(MENU_GROUP_AGENDA, false);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_AGENDA, true);
-            menu.setGroupEnabled(MENU_GROUP_AGENDA, true);
-        }
-
-        if (activity instanceof DayActivity) {
-            menu.setGroupVisible(MENU_GROUP_DAY, true);
-            menu.setGroupEnabled(MENU_GROUP_DAY, false);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_DAY, true);
-            menu.setGroupEnabled(MENU_GROUP_DAY, true);
-        }
-
-        if (activity instanceof WeekActivity) {
-            menu.setGroupVisible(MENU_GROUP_WEEK, true);
-            menu.setGroupEnabled(MENU_GROUP_WEEK, false);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_WEEK, true);
-            menu.setGroupEnabled(MENU_GROUP_WEEK, true);
-        }
-
-        if (activity instanceof MonthActivity) {
-            menu.setGroupVisible(MENU_GROUP_MONTH, true);
-            menu.setGroupEnabled(MENU_GROUP_MONTH, false);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_MONTH, true);
-            menu.setGroupEnabled(MENU_GROUP_MONTH, true);
-        }
-
-        if (activity instanceof EventInfoActivity) {
-            menu.setGroupVisible(MENU_GROUP_TODAY, false);
-            menu.setGroupEnabled(MENU_GROUP_TODAY, false);
-        } else {
-            menu.setGroupVisible(MENU_GROUP_TODAY, true);
-            menu.setGroupEnabled(MENU_GROUP_TODAY, true);
-        }
-    }
-
-    public static boolean onCreateOptionsMenu(Menu menu) {
-
-        MenuItem item;
-        item = menu.add(MENU_GROUP_DAY, MENU_DAY, 0, R.string.day_view);
-        item.setIcon(android.R.drawable.ic_menu_day);
-        item.setAlphabeticShortcut('d');
-
-        item = menu.add(MENU_GROUP_WEEK, MENU_WEEK, 0, R.string.week_view);
-        item.setIcon(android.R.drawable.ic_menu_week);
-        item.setAlphabeticShortcut('w');
-
-        item = menu.add(MENU_GROUP_MONTH, MENU_MONTH, 0, R.string.month_view);
-        item.setIcon(android.R.drawable.ic_menu_month);
-        item.setAlphabeticShortcut('m');
-
-        item = menu.add(MENU_GROUP_AGENDA, MENU_AGENDA, 0, R.string.agenda_view);
-        item.setIcon(android.R.drawable.ic_menu_agenda);
-        item.setAlphabeticShortcut('a');
-
-        item = menu.add(MENU_GROUP_TODAY, MENU_GOTO_TODAY, 0, R.string.goto_today);
-        item.setIcon(android.R.drawable.ic_menu_today);
-        item.setAlphabeticShortcut('t');
-
-        item = menu.add(MENU_GROUP_EVENT_CREATE, MENU_EVENT_CREATE, 0, R.string.event_create);
-        item.setIcon(android.R.drawable.ic_menu_add);
-        item.setAlphabeticShortcut('n');
-
-        item = menu.add(MENU_GROUP_SELECT_CALENDARS, MENU_SELECT_CALENDARS,
-                0, R.string.menu_select_calendars);
-        item.setIcon(android.R.drawable.ic_menu_manage);
-
-        item = menu.add(MENU_GROUP_PREFERENCES, MENU_PREFERENCES, 0, R.string.menu_preferences);
-        item.setIcon(android.R.drawable.ic_menu_preferences);
-        item.setAlphabeticShortcut('p');
-
-        return true;
-    }
-
-    public static boolean onOptionsItemSelected(Activity activity, MenuItem item, Navigator nav) {
-        switch (item.getItemId()) {
-        case MENU_SELECT_CALENDARS: {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.setClass(activity, SelectCalendarsActivity.class);
-            activity.startActivity(intent);
-            return true;
-        }
-        case MENU_GOTO_TODAY:
-            nav.goToToday();
-            return true;
-        case MENU_PREFERENCES:
-            Utils.startActivity(activity, CalendarPreferenceActivity.class.getName(), nav.getSelectedTime());
-            return true;
-        case MENU_AGENDA:
-            Utils.startActivity(activity, AgendaActivity.class.getName(), nav.getSelectedTime());
-            return true;
-        case MENU_DAY:
-            Utils.startActivity(activity, DayActivity.class.getName(), nav.getSelectedTime());
-            return true;
-        case MENU_WEEK:
-            Utils.startActivity(activity, WeekActivity.class.getName(), nav.getSelectedTime());
-            return true;
-        case MENU_MONTH:
-            Utils.startActivity(activity, MonthActivity.class.getName(), nav.getSelectedTime());
-            return true;
-        case MENU_EVENT_CREATE: {
-            long startMillis = nav.getSelectedTime();
-            long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
-            Intent intent = new Intent(Intent.ACTION_EDIT);
-            intent.setClassName(activity, EditEvent.class.getName());
-            intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-            intent.putExtra(EVENT_END_TIME, endMillis);
-            intent.putExtra(EditEvent.EVENT_ALL_DAY, nav.getAllDay());
-            activity.startActivity(intent);
-            return true;
-        }
-        }
-        return false;
-    }
-}
diff --git a/src/com/android/calendar/MonthActivity.java b/src/com/android/calendar/MonthActivity.java
index 4b895e9..c3544d6 100644
--- a/src/com/android/calendar/MonthActivity.java
+++ b/src/com/android/calendar/MonthActivity.java
@@ -20,218 +20,47 @@
 import dalvik.system.VMRuntime;
 
 import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
 import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Calendar.Events;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
 import android.text.format.Time;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.AnimationUtils;
-import android.widget.Gallery.LayoutParams;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.ViewSwitcher;
 
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
-public class MonthActivity extends Activity implements ViewSwitcher.ViewFactory,
-        Navigator, AnimationListener {
+public class MonthActivity extends Activity {
     private static final int INITIAL_HEAP_SIZE = 4 * 1024 * 1024;
-    private Animation mInAnimationPast;
-    private Animation mInAnimationFuture;
-    private Animation mOutAnimationPast;
-    private Animation mOutAnimationFuture;
-    private ViewSwitcher mSwitcher;
     private Time mTime;
-
-    private ContentResolver mContentResolver;
-    EventLoader mEventLoader;
-    private int mStartDay;
-
-    private ProgressBar mProgressBar;
-
-    // This gets run if the time zone is updated in the db
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            // We want mTime to stay on the same day, so we swap the tz
-            mTime.timezone = Utils.getTimeZone(MonthActivity.this, this);
-            mTime.normalize(true);
-            updateTitle(mTime);
-        }
-    };
-
-    private static final int DAY_OF_WEEK_LABEL_IDS[] = {
-        R.id.day0, R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6
-    };
-    private static final int DAY_OF_WEEK_KINDS[] = {
-        Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY,
-        Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY
-    };
-
-    protected void startProgressSpinner() {
-        // start the progress spinner
-        mProgressBar.setVisibility(View.VISIBLE);
-    }
-
-    protected void stopProgressSpinner() {
-        // stop the progress spinner
-        mProgressBar.setVisibility(View.GONE);
-    }
-
-    /* ViewSwitcher.ViewFactory interface methods */
-    public View makeView() {
-        MonthView mv = new MonthView(this, this);
-        mv.setLayoutParams(new ViewSwitcher.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        mv.setSelectedTime(mTime);
-        return mv;
-    }
-
-    public void updateTitle(Time time) {
-        TextView title = (TextView) findViewById(R.id.title);
-        StringBuffer date = new StringBuffer(Utils.formatMonthYear(this, time));
-        if (!TextUtils.equals(Utils.getTimeZone(this, mUpdateTZ), Time.getCurrentTimezone())) {
-            int flags = DateUtils.FORMAT_SHOW_TIME;
-            if (DateFormat.is24HourFormat(this)) {
-                flags |= DateUtils.FORMAT_24HOUR;
-            }
-            long start = System.currentTimeMillis();
-            String tz = Utils.getTimeZone(this, mUpdateTZ);
-            boolean isDST = time.isDst != 0;
-            TimeZone timeZone = TimeZone.getTimeZone(tz);
-            date.append(" (").append(Utils.formatDateRange(this, start, start, flags)).append(" ")
-                    .append(timeZone.getDisplayName(isDST, TimeZone.SHORT, Locale.getDefault()))
-                    .append(")");
-        }
-        title.setText(date.toString());
-    }
-
-    /* Navigator interface methods */
-    public void goTo(Time time, boolean animate) {
-        updateTitle(time);
-
-        MonthView current = (MonthView) mSwitcher.getCurrentView();
-        current.dismissPopup();
-
-        Time currentTime = current.getTime();
-
-        // Compute a month number that is monotonically increasing for any
-        // two adjacent months.
-        // This is faster than calling getSelectedTime() because we avoid
-        // a call to Time#normalize().
-        if (animate) {
-            int currentMonth = currentTime.month + currentTime.year * 12;
-            int nextMonth = time.month + time.year * 12;
-            if (nextMonth < currentMonth) {
-                mSwitcher.setInAnimation(mInAnimationPast);
-                mSwitcher.setOutAnimation(mOutAnimationPast);
-            } else {
-                mSwitcher.setInAnimation(mInAnimationFuture);
-                mSwitcher.setOutAnimation(mOutAnimationFuture);
-            }
-        }
-
-        MonthView next = (MonthView) mSwitcher.getNextView();
-        next.setSelectionMode(current.getSelectionMode());
-        next.setSelectedTime(time);
-        next.reloadEvents();
-        next.animationStarted();
-        mSwitcher.showNext();
-        next.requestFocus();
-        mTime = time;
-    }
-
-    public void goToToday() {
-        Time now = new Time(Utils.getTimeZone(this, mUpdateTZ));
-        now.set(System.currentTimeMillis());
-        now.minute = 0;
-        now.second = 0;
-        now.normalize(false);
-
-        TextView title = (TextView) findViewById(R.id.title);
-        title.setText(Utils.formatMonthYear(this, now));
-        mTime = now;
-
-        MonthView view = (MonthView) mSwitcher.getCurrentView();
-        view.setSelectedTime(now);
-        view.reloadEvents();
-    }
-
-    public long getSelectedTime() {
-        MonthView mv = (MonthView) mSwitcher.getCurrentView();
-        return mv.getSelectedTimeInMillis();
-    }
-
-    public boolean getAllDay() {
-        return false;
-    }
-
-    int getStartDay() {
-        return mStartDay;
-    }
-
-    void eventsChanged() {
-        MonthView view = (MonthView) mSwitcher.getCurrentView();
-        view.reloadEvents();
-    }
+    private MonthFragment mFragment;
 
     /**
      * Listens for intent broadcasts
      */
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_TIME_CHANGED)
-                    || action.equals(Intent.ACTION_DATE_CHANGED)
-                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
-                eventsChanged();
-            }
-        }
-    };
+//FRAG_TODO
+//    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+//        @Override
+//        public void onReceive(Context context, Intent intent) {
+//            String action = intent.getAction();
+//            if (action.equals(Intent.ACTION_TIME_CHANGED)
+//                    || action.equals(Intent.ACTION_DATE_CHANGED)
+//                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
+//                eventsChanged();
+//            }
+//        }
+//    };
 
     // Create an observer so that we can update the views whenever a
     // Calendar event changes.
-    private ContentObserver mObserver = new ContentObserver(new Handler())
-    {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return true;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            eventsChanged();
-        }
-    };
-
-    public void onAnimationStart(Animation animation) {
-    }
-
-    // Notifies the MonthView when an animation has finished.
-    public void onAnimationEnd(Animation animation) {
-        MonthView monthView = (MonthView) mSwitcher.getCurrentView();
-        monthView.animationFinished();
-    }
-
-    public void onAnimationRepeat(Animation animation) {
-    }
+//FRAG_TODO
+//    private ContentObserver mObserver = new ContentObserver(new Handler())
+//    {
+//        @Override
+//        public boolean deliverSelfNotifications() {
+//            return true;
+//        }
+//
+//        @Override
+//        public void onChange(boolean selfChange) {
+//            eventsChanged();
+//        }
+//    };
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -241,8 +70,8 @@
         // TODO: We should restore the old heap size once the activity reaches the idle state
         VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
 
-        setContentView(R.layout.month_activity);
-        mContentResolver = getContentResolver();
+        mFragment = new MonthFragment();
+        openFragmentTransaction().add(android.R.id.content, mFragment).commit();
 
         long time;
         if (icicle != null) {
@@ -251,91 +80,31 @@
             time = Utils.timeFromIntentInMillis(getIntent());
         }
 
-        mTime = new Time(Utils.getTimeZone(this, mUpdateTZ));
+        mTime = new Time();
         mTime.set(time);
         mTime.normalize(true);
-
-        // Get first day of week based on locale and populate the day headers
-        mStartDay = Calendar.getInstance().getFirstDayOfWeek();
-        int diff = mStartDay - Calendar.SUNDAY - 1;
-        final int startDay = Utils.getFirstDayOfWeek();
-        final int sundayColor = getResources().getColor(R.color.sunday_text_color);
-        final int saturdayColor = getResources().getColor(R.color.saturday_text_color);
-
-        for (int day = 0; day < 7; day++) {
-            final String dayString = DateUtils.getDayOfWeekString(
-                    (DAY_OF_WEEK_KINDS[day] + diff) % 7 + 1, DateUtils.LENGTH_MEDIUM);
-            final TextView label = (TextView) findViewById(DAY_OF_WEEK_LABEL_IDS[day]);
-            label.setText(dayString);
-            if (Utils.isSunday(day, startDay)) {
-                label.setTextColor(sundayColor);
-            } else if (Utils.isSaturday(day, startDay)) {
-                label.setTextColor(saturdayColor);
-            }
-        }
-
-        // Set the initial title
-        TextView title = (TextView) findViewById(R.id.title);
-        title.setText(Utils.formatMonthYear(this, mTime));
-
-        mEventLoader = new EventLoader(this);
-        mProgressBar = (ProgressBar) findViewById(R.id.progress_circular);
-
-        mSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
-        mSwitcher.setFactory(this);
-        mSwitcher.getCurrentView().requestFocus();
-
-        mInAnimationPast = AnimationUtils.loadAnimation(this, R.anim.slide_down_in);
-        mOutAnimationPast = AnimationUtils.loadAnimation(this, R.anim.slide_down_out);
-        mInAnimationFuture = AnimationUtils.loadAnimation(this, R.anim.slide_up_in);
-        mOutAnimationFuture = AnimationUtils.loadAnimation(this, R.anim.slide_up_out);
-
-        mInAnimationPast.setAnimationListener(this);
-        mInAnimationFuture.setAnimationListener(this);
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         long timeMillis = Utils.timeFromIntentInMillis(intent);
         if (timeMillis > 0) {
-            Time time = new Time(Utils.getTimeZone(this, mUpdateTZ));
+            Time time = new Time();
             time.set(timeMillis);
-            goTo(time, false);
+//FRAG_TODO fragment not initialized            mFragment.goTo(time, false);
         }
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        if (isFinishing()) {
-            mEventLoader.stopBackgroundThread();
-        }
-        mContentResolver.unregisterContentObserver(mObserver);
-        unregisterReceiver(mIntentReceiver);
-
-        MonthView view = (MonthView) mSwitcher.getCurrentView();
-        view.dismissPopup();
-        view = (MonthView) mSwitcher.getNextView();
-        view.dismissPopup();
-        mEventLoader.stopBackgroundThread();
+//FRAG_TODO        mContentResolver.unregisterContentObserver(mObserver);
+//FRAG_TODO        unregisterReceiver(mIntentReceiver);
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        mUpdateTZ.run();
-        mEventLoader.startBackgroundThread();
-        eventsChanged();
-
-        MonthView view1 = (MonthView) mSwitcher.getCurrentView();
-        MonthView view2 = (MonthView) mSwitcher.getNextView();
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
-        String str = prefs.getString(CalendarPreferenceActivity.KEY_DETAILED_VIEW,
-                CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW);
-        view1.updateView();
-        view2.updateView();
-        view1.setDetailedView(str);
-        view2.setDetailedView(str);
 
         // Register for Intent broadcasts
         IntentFilter filter = new IntentFilter();
@@ -343,36 +112,9 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_DATE_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        registerReceiver(mIntentReceiver, filter);
+//FRAG_TODO        registerReceiver(mIntentReceiver, filter);
 
-        mContentResolver.registerContentObserver(Events.CONTENT_URI,
-                true, mObserver);
-
-        // Record Month View as the (new) start view
-        Utils.setDefaultView(this, CalendarApplication.MONTH_VIEW_ID);
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putLong(EVENT_BEGIN_TIME, mTime.toMillis(true));
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        MenuHelper.onPrepareOptionsMenu(this, menu);
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuHelper.onCreateOptionsMenu(menu);
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        MenuHelper.onOptionsItemSelected(this, item, this);
-        return super.onOptionsItemSelected(item);
+//FRAG_TODO        mContentResolver.registerContentObserver(Events.CONTENT_URI,
+//FRAG_TODO                true, mObserver);
     }
 }
diff --git a/src/com/android/calendar/MonthFragment.java b/src/com/android/calendar/MonthFragment.java
new file mode 100644
index 0000000..ed0d92c
--- /dev/null
+++ b/src/com/android/calendar/MonthFragment.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.month.FullMonthView;
+import com.android.calendar.month.MiniMonthView;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Gallery.LayoutParams;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
+import android.widget.ViewSwitcher.ViewFactory;
+
+import java.util.Calendar;
+
+public class MonthFragment extends Fragment implements CalendarController.EventHandler,
+        AnimationListener, ViewFactory {
+    private static final int DAY_OF_WEEK_LABEL_IDS[] = {
+        R.id.day0, R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6
+    };
+    private static final int DAY_OF_WEEK_KINDS[] = {
+        Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY,
+        Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY
+    };
+    private static final String TAG = "MonthFragment";
+
+    private Time mTime = new Time();
+
+    private Animation mInAnimationPast;
+    private Animation mInAnimationFuture;
+    private Animation mOutAnimationPast;
+    private Animation mOutAnimationFuture;
+    private ViewSwitcher mSwitcher;
+
+    // FRAG_TODO use framework loader
+    private EventLoader mEventLoader;
+
+    private boolean mShowTitle = false;
+    private boolean mUseMiniView = true;
+
+    public MonthFragment() {
+        mTime.setToNow();
+    }
+
+    public MonthFragment(boolean showTitle, long timeMillis, boolean useMiniView) {
+        mShowTitle = showTitle;
+        mUseMiniView = useMiniView;
+        if (timeMillis == 0) {
+            mTime.setToNow();
+        } else {
+            mTime.set(timeMillis);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.month_activity, null);
+
+        mSwitcher = (ViewSwitcher) v.findViewById(R.id.switcher);
+        mSwitcher.setFactory(this);
+        mSwitcher.getCurrentView().requestFocus();
+
+        setDayOfWeekHeader(v);
+
+        return v;
+    }
+
+    private void setDayOfWeekHeader(View v) {
+        Resources res = getActivity().getResources();
+
+        // Get first day of week based on user preference or locale and
+        // populate the day headers
+        final int startDay = Utils.getFirstDayOfWeek(getActivity());
+        int diff = startDay - Calendar.SUNDAY;
+        final int sundayColor = res.getColor(R.color.sunday_text_color);
+        final int saturdayColor = res.getColor(R.color.saturday_text_color);
+
+        for (int day = 0; day < 7; day++) {
+            final String dayString = DateUtils.getDayOfWeekString(
+                    (DAY_OF_WEEK_KINDS[day] + diff) % 7 + 1, DateUtils.LENGTH_MEDIUM);
+            final TextView label = (TextView) v.findViewById(DAY_OF_WEEK_LABEL_IDS[day]);
+            label.setText(dayString);
+            if (Utils.isSunday(day, startDay)) {
+                label.setTextColor(sundayColor);
+            } else if (Utils.isSaturday(day, startDay)) {
+                label.setTextColor(saturdayColor);
+            }
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+        }
+
+        mEventLoader = new EventLoader(getActivity());
+
+        mInAnimationPast = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_down_in);
+        mOutAnimationPast = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_down_out);
+        mInAnimationFuture = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_up_in);
+        mOutAnimationFuture = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_up_out);
+
+        mInAnimationPast.setAnimationListener(this);
+        mInAnimationFuture.setAnimationListener(this);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putLong(EVENT_BEGIN_TIME, mTime.toMillis(true));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mEventLoader.startBackgroundThread();
+        eventsChanged();
+
+        MonthViewInterface current = (MonthViewInterface) mSwitcher.getCurrentView();
+        current.setSelectedTime(mTime);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (getActivity().isFinishing()) {
+            mEventLoader.stopBackgroundThread();
+        }
+
+        mEventLoader.stopBackgroundThread();
+    }
+
+    public View makeView() {
+        Activity activity = getActivity();
+        MiniMonthView mv;
+        if (mUseMiniView) {
+            mv = new MiniMonthView(activity, CalendarController.getInstance(activity),
+                    mEventLoader);
+        } else {
+            mv = new FullMonthView(activity, CalendarController.getInstance(activity),
+                    mEventLoader);
+        }
+        mv.setLayoutParams(new ViewSwitcher.LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        if (mShowTitle) {
+            // TODO Probably not the best place for this. Clean up later.
+
+            // Set the initial title
+            mv.findViewById(R.id.title_bar).setVisibility(View.VISIBLE);
+
+            TextView title = (TextView) mv.findViewById(R.id.title);
+            title.setText(Utils.formatMonthYear(getActivity(), mTime));
+        }
+        return mv;
+    }
+
+    // CalendarController interface
+    public void goTo(Time goToTime, boolean animate) {
+        if (mSwitcher == null) {
+            // The view hasn't been set yet. Just save the time and use it later.
+            mTime.set(goToTime);
+            return;
+        }
+        MonthViewInterface currentView = (MonthViewInterface) mSwitcher.getCurrentView();
+        Time currentTime = currentView.getTime();
+
+        // Only move if we're on a new day
+        if (currentTime.year != goToTime.year || currentTime.yearDay != goToTime.yearDay) {
+            currentView.setSelectedTime(goToTime);
+        }
+
+        mTime.set(goToTime);
+    }
+
+    // CalendarController interface
+    public void goToToday() {
+        Time now = new Time();
+        now.set(System.currentTimeMillis());
+        now.minute = 0;
+        now.second = 0;
+        now.normalize(false);
+        goTo(now, false);
+    }
+
+    // CalendarController interface
+    public long getSelectedTime() {
+        MonthViewInterface mv = (MonthViewInterface) mSwitcher.getCurrentView();
+        return mv.getSelectedTimeInMillis();
+    }
+
+    // CalendarController interface
+    public boolean getAllDay() {
+        return false;
+    }
+
+    // CalendarController interface
+    public void eventsChanged() {
+        MonthViewInterface mv = (MonthViewInterface) mSwitcher.getCurrentView();
+        mv.reloadEvents();
+    }
+
+    public void onAnimationStart(Animation animation) {
+    }
+
+    public void onAnimationEnd(Animation animation) {
+        MonthViewInterface monthView = (MonthViewInterface) mSwitcher.getCurrentView();
+        monthView.animationFinished();
+    }
+
+    public void onAnimationRepeat(Animation animation) {
+    }
+
+    public long getSupportedEventTypes() {
+        return EventType.GO_TO | EventType.EVENTS_CHANGED;
+    }
+
+    public void handleEvent(EventInfo msg) {
+        if (msg.eventType == EventType.GO_TO) {
+// TODO support a range of time
+// TODO support event_id
+// TODO figure out the animate bit
+// TODO support select message
+            goTo(msg.startTime, false);
+        } else if (msg.eventType == EventType.EVENTS_CHANGED) {
+            eventsChanged();
+        }
+    }
+
+    public interface MonthViewInterface {
+        public void reloadEvents();
+        public long getSelectedTimeInMillis();
+        public void setSelectedTime(Time time);
+        public void animationStarted();
+        public void animationFinished();
+        public Time getTime();
+    }
+}
diff --git a/src/com/android/calendar/MonthView.java b/src/com/android/calendar/MonthView.java
deleted file mode 100644
index 076fddf..0000000
--- a/src/com/android/calendar/MonthView.java
+++ /dev/null
@@ -1,1415 +0,0 @@
-/*
- * Copyright (C) 2006 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.calendar;
-
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import static android.provider.Calendar.EVENT_END_TIME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.SparseArray;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-
-public class MonthView extends View implements View.OnCreateContextMenuListener {
-
-    private static final boolean PROFILE_LOAD_TIME = false;
-
-    private static float mScale = 0; // Used for supporting different screen densities
-    private static int WEEK_GAP = 0;
-    private static int MONTH_DAY_GAP = 1;
-    private static float HOUR_GAP = 0f;
-    private static float MIN_EVENT_HEIGHT = 4f;
-    private static int MONTH_DAY_TEXT_SIZE = 20;
-    private static int WEEK_BANNER_HEIGHT = 17;
-    private static int WEEK_TEXT_SIZE = 15;
-    private static int WEEK_TEXT_PADDING = 3;
-    private static int EVENT_DOT_TOP_MARGIN = 5;
-    private static int EVENT_DOT_LEFT_MARGIN = 7;
-    private static int EVENT_DOT_W_H = 10;
-    private static int EVENT_NUM_DAYS = 31;
-    private static int TEXT_TOP_MARGIN = 7;
-    private static int BUSY_BITS_WIDTH = 6;
-    private static int BUSY_BITS_MARGIN = 4;
-    private static int DAY_NUMBER_OFFSET = 10;
-
-    private static int HORIZONTAL_FLING_THRESHOLD = 50;
-
-    private int mCellHeight;
-    private int mBorder;
-    private boolean mLaunchDayView;
-
-    private GestureDetector mGestureDetector;
-
-    private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
-
-    private Time mToday;
-    private Time mViewCalendar;
-    private Time mSavedTime;   // the time when we entered this view
-
-    // This Time object is used to set the time for the other Month view.
-    private Time mOtherViewCalendar;
-
-    // This Time object is used for temporary calculations and is allocated
-    // once to avoid extra garbage collection
-    private Time mTempTime;
-
-    private DayOfMonthCursor mCursor;
-
-    private int mCellWidth;
-
-    private Resources mResources;
-    private MonthActivity mParentActivity;
-    private Navigator mNavigator;
-    private final EventGeometry mEventGeometry;
-
-    // Pre-allocate and reuse
-    private Rect mRect = new Rect();
-
-    //An array of which days have events for quick reference
-    private boolean[] eventDay = new boolean[31];
-
-    private PopupWindow mPopup;
-    private View mPopupView;
-    private static final int POPUP_HEIGHT = 100;
-    private int mPreviousPopupHeight;
-    private static final int POPUP_DISMISS_DELAY = 3000;
-    private DismissPopup mDismissPopup = new DismissPopup();
-
-    // For drawing to an off-screen Canvas
-    private Bitmap mBitmap;
-    private Canvas mCanvas;
-    private boolean mRedrawScreen = true;
-    private Rect mBitmapRect = new Rect();
-    private RectF mRectF = new RectF();
-    private boolean mAnimating;
-
-    // These booleans disable features that were taken out of the spec.
-    private boolean mShowWeekNumbers = false;
-    private boolean mShowToast = false;
-
-    // Bitmap caches.
-    // These improve performance by minimizing calls to NinePatchDrawable.draw() for common
-    // drawables for day backgrounds.
-    // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
-    private SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4);
-
-    private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
-
-    /**
-     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
-     */
-    private static final int SELECTION_HIDDEN = 0;
-    private static final int SELECTION_PRESSED = 1;
-    private static final int SELECTION_SELECTED = 2;
-    private static final int SELECTION_LONGPRESS = 3;
-
-    private int mSelectionMode = SELECTION_HIDDEN;
-
-    /**
-     * The first Julian day of the current month.
-     */
-    private int mFirstJulianDay;
-
-    private int mStartDay;
-
-    private final EventLoader mEventLoader;
-
-    private ArrayList<Event> mEvents = new ArrayList<Event>();
-
-    // Cached colors
-    private int mMonthOtherMonthColor;
-    private int mMonthWeekBannerColor;
-    private int mMonthOtherMonthBannerColor;
-    private int mMonthOtherMonthDayNumberColor;
-    private int mMonthDayNumberColor;
-    private int mMonthTodayNumberColor;
-    private int mMonthTodayBackgroundColor;
-    private int mMonthSaturdayColor;
-    private int mMonthSundayColor;
-    private int mBusybitsColor;
-    private int mMonthBgColor;
-    private int mPressedColor;
-
-    // Accessibility support related members
-
-    private int mPrevSelectedDayOfMonth;
-    private int mPrevSelectedMonth;
-    private CharSequence mPrevTitleTextViewText;
-
-    // This gets run if the time zone is updated in the db
-    private Runnable mUpdateTZ = new Runnable() {
-        @Override
-        public void run() {
-            String tz = Utils.getTimeZone(mContext, this);
-            // These fields we want to keep the same time represented
-            mSavedTime.switchTimezone(tz);
-            mToday.switchTimezone(tz);
-            mTempTime.switchTimezone(tz);
-
-            // These fields we want to keep the same day represented
-            mViewCalendar.timezone = tz;
-            mViewCalendar.normalize(true);
-            mOtherViewCalendar.timezone = tz;
-            mOtherViewCalendar.normalize(true);
-        }
-    };
-
-    public MonthView(MonthActivity activity, Navigator navigator) {
-        super(activity);
-        if (mScale == 0) {
-            mScale = getContext().getResources().getDisplayMetrics().density;
-           if (mScale != 1) {
-                    WEEK_GAP *= mScale;
-                    MONTH_DAY_GAP *= mScale;
-                    HOUR_GAP *= mScale;
-
-                    MONTH_DAY_TEXT_SIZE *= mScale;
-                    WEEK_BANNER_HEIGHT *= mScale;
-                    WEEK_TEXT_SIZE *= mScale;
-                    WEEK_TEXT_PADDING *= mScale;
-                    EVENT_DOT_TOP_MARGIN *= mScale;
-                    EVENT_DOT_LEFT_MARGIN *= mScale;
-                    EVENT_DOT_W_H *= mScale;
-                    TEXT_TOP_MARGIN *= mScale;
-                    HORIZONTAL_FLING_THRESHOLD *= mScale;
-                    MIN_EVENT_HEIGHT *= mScale;
-                    BUSY_BITS_WIDTH *= mScale;
-                    BUSY_BITS_MARGIN *= mScale;
-                    DAY_NUMBER_OFFSET *= mScale;
-                }
-            }
-
-        String tz = Utils.getTimeZone(mContext, mUpdateTZ);
-        mSavedTime = new Time(tz);
-        mOtherViewCalendar = new Time(tz);
-        mTempTime = new Time(tz);
-
-        mEventLoader = activity.mEventLoader;
-        mNavigator = navigator;
-        mEventGeometry = new EventGeometry();
-        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
-        mEventGeometry.setHourGap(HOUR_GAP);
-        init(activity, tz);
-    }
-
-    private void init(MonthActivity activity, String timeZone) {
-        setFocusable(true);
-        setClickable(true);
-        setOnCreateContextMenuListener(this);
-        mParentActivity = activity;
-        mViewCalendar = new Time(timeZone);
-        long now = System.currentTimeMillis();
-        mViewCalendar.set(now);
-        mViewCalendar.monthDay = 1;
-        long millis = mViewCalendar.normalize(true /* ignore DST */);
-        mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
-        mStartDay = Utils.getFirstDayOfWeek();
-        mViewCalendar.set(now);
-
-        mCursor = new DayOfMonthCursor(mViewCalendar.year,  mViewCalendar.month,
-                mViewCalendar.monthDay, mParentActivity.getStartDay());
-        mToday = new Time(timeZone);
-        mToday.set(System.currentTimeMillis());
-
-        mResources = activity.getResources();
-
-        // Cache color lookups
-        Resources res = getResources();
-        mMonthOtherMonthColor = res.getColor(R.color.month_other_month);
-        mMonthWeekBannerColor = res.getColor(R.color.month_week_banner);
-        mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner);
-        mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number);
-        mMonthDayNumberColor = res.getColor(R.color.month_day_number);
-        mMonthTodayNumberColor = res.getColor(R.color.month_today_number);
-        mMonthTodayBackgroundColor = res.getColor(R.color.month_today_bgcolor);
-        mMonthSaturdayColor = res.getColor(R.color.month_saturday);
-        mMonthSundayColor = res.getColor(R.color.month_sunday);
-        mBusybitsColor = res.getColor(R.color.month_busybits);
-        mMonthBgColor = res.getColor(R.color.month_bgcolor);
-        mPressedColor = res.getColor(R.color.pressed);
-
-        if (mShowToast) {
-            LayoutInflater inflater;
-            inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            mPopupView = inflater.inflate(R.layout.month_bubble, null);
-            mPopup = new PopupWindow(activity);
-            mPopup.setContentView(mPopupView);
-            Resources.Theme dialogTheme = getResources().newTheme();
-            dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
-            TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
-                android.R.attr.windowBackground });
-            mPopup.setBackgroundDrawable(ta.getDrawable(0));
-            ta.recycle();
-        }
-
-        mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
-            @Override
-            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
-                    float velocityY) {
-                // The user might do a slow "fling" after touching the screen
-                // and we don't want the long-press to pop up a context menu.
-                // Setting mLaunchDayView to false prevents the long-press.
-                mLaunchDayView = false;
-                mSelectionMode = SELECTION_HIDDEN;
-
-                int distanceX = Math.abs((int) e2.getX() - (int) e1.getX());
-                int distanceY = Math.abs((int) e2.getY() - (int) e1.getY());
-                if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) {
-                    return false;
-                }
-
-                // Switch to a different month
-                Time time = mOtherViewCalendar;
-                time.set(mViewCalendar);
-                if (velocityY < 0) {
-                    time.month += 1;
-                } else {
-                    time.month -= 1;
-                }
-                time.normalize(true);
-                mParentActivity.goTo(time, true);
-
-                return true;
-            }
-
-            @Override
-            public boolean onDown(MotionEvent e) {
-                // Launch the Day/Agenda view when the finger lifts up,
-                // unless the finger moves before lifting up (onFling or onScroll).
-                mLaunchDayView = true;
-                return true;
-            }
-
-            public void setSelectedCell(MotionEvent e) {
-                int x = (int) e.getX();
-                int y = (int) e.getY();
-                int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
-                int col = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
-                if (row > 5) {
-                    row = 5;
-                }
-                if (col > 6) {
-                    col = 6;
-                }
-
-                // Highlight the selected day.
-                mCursor.setSelectedRowColumn(row, col);
-            }
-
-            @Override
-            public void onShowPress(MotionEvent e) {
-                // Highlight the selected day.
-                setSelectedCell(e);
-                mSelectionMode = SELECTION_PRESSED;
-                mRedrawScreen = true;
-                invalidate();
-            }
-
-            @Override
-            public void onLongPress(MotionEvent e) {
-                // If mLaunchDayView is true, then we haven't done any scrolling
-                // after touching the screen, so allow long-press to proceed
-                // with popping up the context menu.
-                if (mLaunchDayView) {
-                    mLaunchDayView = false;
-                    mSelectionMode = SELECTION_LONGPRESS;
-                    mRedrawScreen = true;
-                    invalidate();
-                    performLongClick();
-                }
-            }
-
-            @Override
-            public boolean onScroll(MotionEvent e1, MotionEvent e2,
-                    float distanceX, float distanceY) {
-                // If the user moves his finger after touching, then do not
-                // launch the Day view when he lifts his finger.  Also, turn
-                // off the selection.
-                mLaunchDayView = false;
-
-                if (mSelectionMode != SELECTION_HIDDEN) {
-                    mSelectionMode = SELECTION_HIDDEN;
-                    mRedrawScreen = true;
-                    invalidate();
-                }
-                return true;
-            }
-
-            @Override
-            public boolean onSingleTapUp(MotionEvent e) {
-                if (mLaunchDayView) {
-                    setSelectedCell(e);
-                    mSelectionMode = SELECTION_SELECTED;
-                    mRedrawScreen = true;
-                    invalidate();
-                    mLaunchDayView = false;
-                    int x = (int) e.getX();
-                    int y = (int) e.getY();
-                    long millis = getSelectedMillisFor(x, y);
-                    Utils.startActivity(getContext(), mDetailedView, millis);
-                }
-
-                return true;
-            }
-        });
-    }
-
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        MenuItem item;
-
-        final long startMillis = getSelectedTimeInMillis();
-        final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
-                | DateUtils.FORMAT_ABBREV_MONTH;
-
-        final String title = Utils.formatDateRange(mParentActivity, startMillis, startMillis,
-                flags);
-        menu.setHeaderTitle(title);
-
-        item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
-        item.setOnMenuItemClickListener(mContextMenuHandler);
-        item.setIcon(android.R.drawable.ic_menu_day);
-        item.setAlphabeticShortcut('d');
-
-        item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
-        item.setOnMenuItemClickListener(mContextMenuHandler);
-        item.setIcon(android.R.drawable.ic_menu_agenda);
-        item.setAlphabeticShortcut('a');
-
-        item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
-        item.setOnMenuItemClickListener(mContextMenuHandler);
-        item.setIcon(android.R.drawable.ic_menu_add);
-        item.setAlphabeticShortcut('n');
-    }
-
-    private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
-        public boolean onMenuItemClick(MenuItem item) {
-            switch (item.getItemId()) {
-                case MenuHelper.MENU_DAY: {
-                    long startMillis = getSelectedTimeInMillis();
-                    Utils.startActivity(mParentActivity, DayActivity.class.getName(), startMillis);
-                    break;
-                }
-                case MenuHelper.MENU_AGENDA: {
-                    long startMillis = getSelectedTimeInMillis();
-                    Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
-                    break;
-                }
-                case MenuHelper.MENU_EVENT_CREATE: {
-                    long startMillis = getSelectedTimeInMillis();
-                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    intent.setClassName(mParentActivity, EditEvent.class.getName());
-                    intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-                    intent.putExtra(EVENT_END_TIME, endMillis);
-                    mParentActivity.startActivity(intent);
-                    break;
-                }
-                default: {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    void reloadEvents() {
-        // Get the date for the beginning of the month
-        Time monthStart = mTempTime;
-        monthStart.set(mViewCalendar);
-        monthStart.monthDay = 1;
-        monthStart.hour = 0;
-        monthStart.minute = 0;
-        monthStart.second = 0;
-        long millis = monthStart.normalize(true /* ignore isDst */);
-        int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
-
-        // Load the days with events in the background
-        mParentActivity.startProgressSpinner();
-        final long startMillis;
-        if (PROFILE_LOAD_TIME) {
-            startMillis = SystemClock.uptimeMillis();
-        } else {
-            // To avoid a compiler error that this variable might not be initialized.
-            startMillis = 0;
-        }
-
-        final ArrayList<Event> events = new ArrayList<Event>();
-        mEventLoader.loadEventsInBackground(EVENT_NUM_DAYS, events, millis, new Runnable() {
-            public void run() {
-                mEvents = events;
-                mRedrawScreen = true;
-                mParentActivity.stopProgressSpinner();
-                invalidate();
-                int numEvents = events.size();
-
-                // Clear out event days
-                for (int i = 0; i < EVENT_NUM_DAYS; i++) {
-                    eventDay[i] = false;
-                }
-
-                // Compute the new set of days with events
-                for (int i = 0; i < numEvents; i++) {
-                    Event event = events.get(i);
-                    int startDay = event.startDay - mFirstJulianDay;
-                    int endDay = event.endDay - mFirstJulianDay + 1;
-                    if (startDay < 31 || endDay >= 0) {
-                        if (startDay < 0) {
-                            startDay = 0;
-                        }
-                        if (startDay > 31) {
-                            startDay = 31;
-                        }
-                        if (endDay < 0) {
-                            endDay = 0;
-                        }
-                        if (endDay > 31) {
-                            endDay = 31;
-                        }
-                        for (int j = startDay; j < endDay; j++) {
-                            eventDay[j] = true;
-                        }
-                    }
-                }
-            }
-        }, null);
-    }
-
-    void animationStarted() {
-        mAnimating = true;
-    }
-
-    void animationFinished() {
-        mAnimating = false;
-        mRedrawScreen = true;
-        invalidate();
-    }
-
-    @Override
-    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
-        drawingCalc(width, height);
-        // If the size changed, then we should rebuild the bitmaps...
-        clearBitmapCache();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        // No need to hang onto the bitmaps...
-        clearBitmapCache();
-        if (mBitmap != null) {
-            mBitmap.recycle();
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mRedrawScreen) {
-            if (mCanvas == null) {
-                drawingCalc(getWidth(), getHeight());
-            }
-
-            // If we are zero-sized, the canvas will remain null so check again
-            if (mCanvas != null) {
-                // Clear the background
-                final Canvas bitmapCanvas = mCanvas;
-                bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
-                doDraw(bitmapCanvas);
-                mRedrawScreen = false;
-            }
-        }
-
-        // If we are zero-sized, the bitmap will be null so guard against this
-        if (mBitmap != null) {
-            canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null);
-        }
-
-        sendAccessibilityEvents();
-    }
-
-    private void doDraw(Canvas canvas) {
-        boolean isLandscape = getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
-
-        Paint p = new Paint();
-        Rect r = mRect;
-        int columnDay1 = mCursor.getColumnOf(1);
-
-        // Get the Julian day for the date at row 0, column 0.
-        int day = mFirstJulianDay - columnDay1;
-
-        int weekNum = 0;
-        Calendar calendar = null;
-        if (mShowWeekNumbers) {
-            calendar = Calendar.getInstance();
-            boolean noPrevMonth = (columnDay1 == 0);
-
-            // Compute the week number for the first row.
-            weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
-        }
-
-        for (int row = 0; row < 6; row++) {
-            for (int column = 0; column < 7; column++) {
-                drawBox(day, weekNum, row, column, canvas, p, r, isLandscape);
-                day += 1;
-            }
-
-            if (mShowWeekNumbers) {
-                weekNum += 1;
-                if (weekNum >= 53) {
-                    boolean inCurrentMonth = (day - mFirstJulianDay < 31);
-                    weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
-                }
-            }
-        }
-
-        drawGrid(canvas, p);
-    }
-
-    private void sendAccessibilityEvents() {
-        if (!isShown() || !AccessibilityManager.getInstance(mContext).isEnabled()) {
-            return;
-        }
-        // if the title text has changed => announce period
-        TextView titleView = (TextView) mParentActivity.findViewById(R.id.title);
-        CharSequence titleTextViewText = titleView.getText();
-        // intended use of identity comparison
-        boolean titleChanged = titleTextViewText != mPrevTitleTextViewText;
-        if (titleChanged) {
-            mPrevTitleTextViewText = titleTextViewText;
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-        int selectedMonth = mCursor.getMonth();
-        int selectedDayOfMonth = mCursor.getSelectedDayOfMonth();
-        // if title or selection has changed => announce selection
-        // Note: if the title has changed we want to send both events
-        if (titleChanged || mPrevSelectedMonth != selectedMonth
-                || mPrevSelectedDayOfMonth != selectedDayOfMonth) {
-            mPrevSelectedMonth = selectedMonth;
-            mPrevSelectedDayOfMonth = selectedDayOfMonth;
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mGestureDetector.onTouchEvent(event)) {
-            return true;
-        }
-
-        return super.onTouchEvent(event);
-    }
-
-    @Override
-    public void sendAccessibilityEvent(int eventType) {
-        // do not send focus events since we are not focused every time we
-        // are shown and the corresponding activity will make the announcement
-        if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
-            return;
-        }
-        super.sendAccessibilityEvent(eventType);
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-            // the variable has been updated before we are called here
-            // so we avoid calling findViewById
-            event.getText().add(mPrevTitleTextViewText);
-        } else if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SELECTED) {
-            // add the selection
-            long startMillis = getSelectedTimeInMillis();
-            int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
-                    | DateUtils.FORMAT_ABBREV_MONTH;
-            String text = Utils.formatDateRange(mParentActivity, startMillis, startMillis, flags);
-            event.getText().add(text);
-
-            // add event count
-            int todayEventCount = 0;
-            int selectedDayOfMonth = mCursor.getSelectedDayOfMonth();
-            for (int i = 0, count = mEvents.size(); i < count; i++) {
-                Event calendarEvent = mEvents.get(i);
-                int startDay = calendarEvent.startDay - mFirstJulianDay + 1;
-                int endDay = calendarEvent.endDay - mFirstJulianDay + 1;
-                if (startDay <= selectedDayOfMonth && endDay >= selectedDayOfMonth) {
-                    todayEventCount++;
-                }
-            }
-            // set only the event count for the selected date since neither
-            // there is a selected event nor the count of events per month
-            // brings useful information (as opposed to CalendarView)
-            event.setItemCount(todayEventCount);
-        }
-
-        return true;
-    }
-
-    private long getSelectedMillisFor(int x, int y) {
-        int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
-        int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
-        if (column > 6) {
-            column = 6;
-        }
-
-        DayOfMonthCursor c = mCursor;
-        Time time = mTempTime;
-        time.set(mViewCalendar);
-
-        // Compute the day number from the row and column.  If the row and
-        // column are in a different month from the current one, then the
-        // monthDay might be negative or it might be greater than the number
-        // of days in this month, but that is okay because the normalize()
-        // method will adjust the month (and year) if necessary.
-        time.monthDay = 7 * row + column - c.getOffset() + 1;
-        return time.normalize(true);
-    }
-
-    /**
-     * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
-     *
-     * @param drawable the drawable we wish to render
-     * @param width the width of the resulting bitmap
-     * @param height the height of the resulting bitmap
-     * @return a new bitmap
-     */
-    private Bitmap createBitmap(Drawable drawable, int width, int height) {
-        // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
-        Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
-
-        // Draw the drawable into the bitmap at the origin.
-        Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(0, 0, width, height);
-        drawable.draw(canvas);
-        return bitmap;
-    }
-
-    /**
-     * Clears the bitmap cache. Generally only needed when the screen size changed.
-     */
-    private void clearBitmapCache() {
-        recycleAndClearBitmapCache(mDayBitmapCache);
-    }
-
-    private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) {
-        int size = bitmapCache.size();
-        for(int i = 0; i < size; i++) {
-            bitmapCache.valueAt(i).recycle();
-        }
-        bitmapCache.clear();
-
-    }
-
-    /**
-     * Draw the grid lines for the calendar
-     * @param canvas The canvas to draw on.
-     * @param p The paint used for drawing.
-     */
-    private void drawGrid(Canvas canvas, Paint p) {
-        p.setColor(mMonthWeekBannerColor);
-        p.setAntiAlias(false);
-
-        final int width = getMeasuredWidth();
-        final int height = getMeasuredHeight();
-
-        for (int row = 0; row < 6; row++) {
-            int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1;
-            canvas.drawLine(0, y, width, y, p);
-        }
-        for (int column = 1; column < 7; column++) {
-            int x = mBorder + column * (MONTH_DAY_GAP + mCellWidth) - 1;
-            canvas.drawLine(x, WEEK_GAP, x, height, p);
-        }
-    }
-
-    /**
-     * Draw a single box onto the canvas.
-     * @param day The Julian day.
-     * @param weekNum The week number.
-     * @param row The row of the box (0-5).
-     * @param column The column of the box (0-6).
-     * @param canvas The canvas to draw on.
-     * @param p The paint used for drawing.
-     * @param r The rectangle used for each box.
-     * @param isLandscape Is the current orientation landscape.
-     */
-    private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
-            Rect r, boolean isLandscape) {
-
-        // Only draw the selection if we are in the press state or if we have
-        // moved the cursor with key input.
-        boolean drawSelection = false;
-        if (mSelectionMode != SELECTION_HIDDEN) {
-            drawSelection = mCursor.isSelected(row, column);
-        }
-
-        boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column);
-        boolean isToday = false;
-        int dayOfBox = mCursor.getDayAt(row, column);
-        if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year
-                && mCursor.getMonth() == mToday.month) {
-            isToday = true;
-        }
-
-        int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
-        int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
-
-        r.left = x;
-        r.top = y;
-        r.right = x + mCellWidth;
-        r.bottom = y + mCellHeight;
-
-
-        // Adjust the left column, right column, and bottom row to leave
-        // no border.
-        if (column == 0) {
-            r.left = -1;
-        } else if (column == 6) {
-            r.right += mBorder + 2;
-        }
-
-        if (row == 5) {
-            r.bottom = getMeasuredHeight();
-        }
-
-
-        // Draw the cell contents (excluding monthDay number)
-        if (!withinCurrentMonth) {
-            // Adjust cell boundaries to compensate for the different border
-            // style.
-            r.top--;
-            if (column != 0) {
-                r.left--;
-            }
-            p.setStyle(Style.FILL);
-            p.setColor(mMonthOtherMonthColor);
-            canvas.drawRect(r, p);
-        } else if (drawSelection) {
-            p.setStyle(Style.FILL);
-            p.setColor(mPressedColor);
-            canvas.drawRect(r, p);
-
-            //Places events for that day
-            drawEvents(day, canvas, r, p, true /*draw bb background*/);
-            if (!mAnimating) {
-                updateEventDetails(day);
-            }
-        } else {
-            // Today gets a different background
-            if (isToday) {
-                p.setStyle(Style.FILL);
-                p.setColor(mMonthTodayBackgroundColor);
-                canvas.drawRect(r, p);
-            }
-            //Places events for that day
-            drawEvents(day, canvas, r, p, true /*draw bb background*/);
-        }
-
-        // Draw week number
-        if (mShowWeekNumbers && column == 0) {
-            // Draw the banner
-            p.setStyle(Paint.Style.FILL);
-            p.setColor(mMonthWeekBannerColor);
-            if (isLandscape) {
-                int bottom = r.bottom;
-                r.bottom = r.top + WEEK_BANNER_HEIGHT;
-                r.left++;
-                canvas.drawRect(r, p);
-                r.bottom = bottom;
-                r.left--;
-            } else {
-                int top = r.top;
-                r.top = r.bottom - WEEK_BANNER_HEIGHT;
-                r.left++;
-                canvas.drawRect(r, p);
-                r.top = top;
-                r.left--;
-            }
-
-            // Draw the number
-            p.setColor(mMonthOtherMonthBannerColor);
-            p.setAntiAlias(true);
-            p.setTypeface(null);
-            p.setTextSize(WEEK_TEXT_SIZE);
-            p.setTextAlign(Paint.Align.LEFT);
-
-            int textX = r.left + WEEK_TEXT_PADDING;
-            int textY;
-            if (isLandscape) {
-                textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
-            } else {
-                textY = r.bottom - WEEK_TEXT_PADDING;
-            }
-
-            canvas.drawText(String.valueOf(weekNum), textX, textY, p);
-        }
-
-        // Draw the monthDay number
-        p.setStyle(Paint.Style.FILL);
-        p.setAntiAlias(true);
-        p.setTypeface(null);
-        p.setTextSize(MONTH_DAY_TEXT_SIZE);
-
-        if (!withinCurrentMonth) {
-            p.setColor(mMonthOtherMonthDayNumberColor);
-        } else {
-            if (isToday && !drawSelection) {
-                p.setColor(mMonthTodayNumberColor);
-            } else if (Utils.isSunday(column, mStartDay)) {
-                p.setColor(mMonthSundayColor);
-            } else if (Utils.isSaturday(column, mStartDay)) {
-                p.setColor(mMonthSaturdayColor);
-            } else {
-                p.setColor(mMonthDayNumberColor);
-            }
-            //bolds the day if there's an event that day
-            p.setFakeBoldText(eventDay[day-mFirstJulianDay]);
-        }
-        /*Drawing of day number is done here
-         *easy to find tags draw number draw day*/
-        p.setTextAlign(Paint.Align.CENTER);
-        // center of text
-        int textX = r.left + (r.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH - r.left) / 2;
-        int textY = (int) (r.top + p.getTextSize() + TEXT_TOP_MARGIN); // bottom of text
-        canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p);
-    }
-
-    ///Create and draw the event busybits for this day
-    private void drawEvents(int date, Canvas canvas, Rect rect, Paint p, boolean drawBg) {
-        // The top of the busybits section lines up with the top of the day number
-        int top = rect.top + TEXT_TOP_MARGIN + BUSY_BITS_MARGIN;
-        int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
-
-        Style oldStyle = p.getStyle();
-        int oldColor = p.getColor();
-
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        EventGeometry geometry = mEventGeometry;
-
-        if (drawBg) {
-            RectF rf = mRectF;
-            rf.left = left;
-            rf.right = left + BUSY_BITS_WIDTH;
-            rf.bottom = rect.bottom - BUSY_BITS_MARGIN;
-            rf.top = top;
-
-            p.setColor(mMonthBgColor);
-            p.setStyle(Style.FILL);
-            canvas.drawRect(rf, p);
-        }
-
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-            if (!geometry.computeEventRect(date, left, top, BUSY_BITS_WIDTH, event)) {
-                continue;
-            }
-            drawEventRect(rect, event, canvas, p);
-        }
-
-    }
-
-    // Draw busybits for a single event
-    private RectF drawEventRect(Rect rect, Event event, Canvas canvas, Paint p) {
-
-        p.setColor(mBusybitsColor);
-
-        int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
-        int bottom = rect.bottom - BUSY_BITS_MARGIN;
-
-        RectF rf = mRectF;
-        rf.top = event.top;
-        // Make sure we don't go below the bottom of the bb bar
-        rf.bottom = Math.min(event.bottom, bottom);
-        rf.left = left;
-        rf.right = left + BUSY_BITS_WIDTH;
-
-        canvas.drawRect(rf, p);
-
-        return rf;
-    }
-
-    private boolean isFirstDayOfNextMonth(int row, int column) {
-        if (column == 0) {
-            column = 6;
-            row--;
-        } else {
-            column--;
-        }
-        return mCursor.isWithinCurrentMonth(row, column);
-    }
-
-    private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
-            Calendar calendar) {
-        calendar.set(Calendar.DAY_OF_MONTH, mCursor.getDayAt(row, column));
-        if (isWithinCurrentMonth) {
-            calendar.set(Calendar.MONTH, mCursor.getMonth());
-            calendar.set(Calendar.YEAR, mCursor.getYear());
-        } else {
-            int month = mCursor.getMonth();
-            int year = mCursor.getYear();
-            if (row < 2) {
-                // Previous month
-                if (month == 0) {
-                    year--;
-                    month = 11;
-                } else {
-                    month--;
-                }
-            } else {
-                // Next month
-                if (month == 11) {
-                    year++;
-                    month = 0;
-                } else {
-                    month++;
-                }
-            }
-            calendar.set(Calendar.MONTH, month);
-            calendar.set(Calendar.YEAR, year);
-        }
-
-        return calendar.get(Calendar.WEEK_OF_YEAR);
-    }
-
-    void setDetailedView(String detailedView) {
-        mDetailedView = detailedView;
-    }
-
-    void setSelectedTime(Time time) {
-        // Save the selected time so that we can restore it later when we switch views.
-        mSavedTime.set(time);
-
-        mViewCalendar.set(time);
-        mViewCalendar.monthDay = 1;
-        long millis = mViewCalendar.normalize(true /* ignore DST */);
-        mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
-        mViewCalendar.set(time);
-
-        mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
-                mCursor.getWeekStartDay());
-
-        mRedrawScreen = true;
-        invalidate();
-    }
-
-    public long getSelectedTimeInMillis() {
-        Time time = mTempTime;
-        time.set(mViewCalendar);
-
-        time.month += mCursor.getSelectedMonthOffset();
-        time.monthDay = mCursor.getSelectedDayOfMonth();
-
-        // Restore the saved hour:minute:second offset from when we entered
-        // this view.
-        time.second = mSavedTime.second;
-        time.minute = mSavedTime.minute;
-        time.hour = mSavedTime.hour;
-        return time.normalize(true);
-    }
-
-    Time getTime() {
-        return mViewCalendar;
-    }
-
-    public int getSelectionMode() {
-        return mSelectionMode;
-    }
-
-    public void setSelectionMode(int selectionMode) {
-        mSelectionMode = selectionMode;
-    }
-
-    private void drawingCalc(int width, int height) {
-        mCellHeight = (height - (6 * WEEK_GAP)) / 6;
-        mEventGeometry
-                .setHourHeight((mCellHeight - BUSY_BITS_MARGIN * 2 - TEXT_TOP_MARGIN) / 24.0f);
-        mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7;
-        mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2;
-
-        if (mShowToast) {
-            mPopup.dismiss();
-            mPopup.setWidth(width - 20);
-            mPopup.setHeight(POPUP_HEIGHT);
-        }
-
-        if (((mBitmap == null)
-                    || mBitmap.isRecycled()
-                    || (mBitmap.getHeight() != height)
-                    || (mBitmap.getWidth() != width))
-                && (width > 0) && (height > 0)) {
-            if (mBitmap != null) {
-                mBitmap.recycle();
-            }
-            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            mCanvas = new Canvas(mBitmap);
-        }
-
-        mBitmapRect.top = 0;
-        mBitmapRect.bottom = height;
-        mBitmapRect.left = 0;
-        mBitmapRect.right = width;
-    }
-
-    private void updateEventDetails(int date) {
-        if (!mShowToast) {
-            return;
-        }
-
-        getHandler().removeCallbacks(mDismissPopup);
-        ArrayList<Event> events = mEvents;
-        int numEvents = events.size();
-        if (numEvents == 0) {
-            mPopup.dismiss();
-            return;
-        }
-
-        int eventIndex = 0;
-        for (int i = 0; i < numEvents; i++) {
-            Event event = events.get(i);
-
-            if (event.startDay > date || event.endDay < date) {
-                continue;
-            }
-
-            // If we have all the event that we can display, then just count
-            // the extra ones.
-            if (eventIndex >= 4) {
-                eventIndex += 1;
-                continue;
-            }
-
-            int flags;
-            boolean showEndTime = false;
-            if (event.allDay) {
-                int numDays = event.endDay - event.startDay;
-                if (numDays == 0) {
-                    flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
-                            | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
-                } else {
-                    showEndTime = true;
-                    flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
-                            | DateUtils.FORMAT_ABBREV_ALL;
-                }
-            } else {
-                flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
-                if (DateFormat.is24HourFormat(mParentActivity)) {
-                    flags |= DateUtils.FORMAT_24HOUR;
-                }
-            }
-
-            String timeRange;
-            if (showEndTime) {
-                timeRange = Utils.formatDateRange(mParentActivity,
-                        event.startMillis, event.endMillis, flags);
-            } else {
-                timeRange = Utils.formatDateRange(mParentActivity,
-                        event.startMillis, event.startMillis, flags);
-            }
-
-            TextView timeView = null;
-            TextView titleView = null;
-            switch (eventIndex) {
-                case 0:
-                    timeView = (TextView) mPopupView.findViewById(R.id.time0);
-                    titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
-                    break;
-                case 1:
-                    timeView = (TextView) mPopupView.findViewById(R.id.time1);
-                    titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
-                    break;
-                case 2:
-                    timeView = (TextView) mPopupView.findViewById(R.id.time2);
-                    titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
-                    break;
-                case 3:
-                    timeView = (TextView) mPopupView.findViewById(R.id.time3);
-                    titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
-                    break;
-            }
-
-            timeView.setText(timeRange);
-            titleView.setText(event.title);
-            eventIndex += 1;
-        }
-        if (eventIndex == 0) {
-            // We didn't find any events for this day
-            mPopup.dismiss();
-            return;
-        }
-
-        // Hide the items that have no event information
-        View view;
-        switch (eventIndex) {
-            case 1:
-                view = mPopupView.findViewById(R.id.item_layout1);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.item_layout2);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.item_layout3);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.plus_more);
-                view.setVisibility(View.GONE);
-                break;
-            case 2:
-                view = mPopupView.findViewById(R.id.item_layout1);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout2);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.item_layout3);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.plus_more);
-                view.setVisibility(View.GONE);
-                break;
-            case 3:
-                view = mPopupView.findViewById(R.id.item_layout1);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout2);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout3);
-                view.setVisibility(View.GONE);
-                view = mPopupView.findViewById(R.id.plus_more);
-                view.setVisibility(View.GONE);
-                break;
-            case 4:
-                view = mPopupView.findViewById(R.id.item_layout1);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout2);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout3);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.plus_more);
-                view.setVisibility(View.GONE);
-                break;
-            default:
-                view = mPopupView.findViewById(R.id.item_layout1);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout2);
-                view.setVisibility(View.VISIBLE);
-                view = mPopupView.findViewById(R.id.item_layout3);
-                view.setVisibility(View.VISIBLE);
-                TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more);
-                tv.setVisibility(View.VISIBLE);
-                String format = mResources.getString(R.string.plus_N_more);
-                String plusMore = String.format(format, eventIndex - 4);
-                tv.setText(plusMore);
-                break;
-        }
-
-        if (eventIndex > 5) {
-            eventIndex = 5;
-        }
-        int popupHeight = 20 * eventIndex + 15;
-        mPopup.setHeight(popupHeight);
-
-        if (mPreviousPopupHeight != popupHeight) {
-            mPreviousPopupHeight = popupHeight;
-            mPopup.dismiss();
-        }
-        mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
-        postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        long duration = event.getEventTime() - event.getDownTime();
-
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_DPAD_CENTER:
-            if (mSelectionMode == SELECTION_HIDDEN) {
-                // Don't do anything unless the selection is visible.
-                break;
-            }
-
-            if (mSelectionMode == SELECTION_PRESSED) {
-                // This was the first press when there was nothing selected.
-                // Change the selection from the "pressed" state to the
-                // the "selected" state.  We treat short-press and
-                // long-press the same here because nothing was selected.
-                mSelectionMode = SELECTION_SELECTED;
-                mRedrawScreen = true;
-                invalidate();
-                break;
-            }
-
-            // Check the duration to determine if this was a short press
-            if (duration < ViewConfiguration.getLongPressTimeout()) {
-                long millis = getSelectedTimeInMillis();
-                Utils.startActivity(getContext(), mDetailedView, millis);
-            } else {
-                mSelectionMode = SELECTION_LONGPRESS;
-                mRedrawScreen = true;
-                invalidate();
-                performLongClick();
-            }
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mSelectionMode == SELECTION_HIDDEN) {
-            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
-                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
-                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                // Display the selection box but don't move or select it
-                // on this key press.
-                mSelectionMode = SELECTION_SELECTED;
-                mRedrawScreen = true;
-                invalidate();
-                return true;
-            } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
-                // Display the selection box but don't select it
-                // on this key press.
-                mSelectionMode = SELECTION_PRESSED;
-                mRedrawScreen = true;
-                invalidate();
-                return true;
-            }
-        }
-
-        mSelectionMode = SELECTION_SELECTED;
-        boolean redraw = false;
-        Time other = null;
-
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_ENTER:
-            long millis = getSelectedTimeInMillis();
-            Utils.startActivity(getContext(), mDetailedView, millis);
-            return true;
-        case KeyEvent.KEYCODE_DPAD_UP:
-            if (mCursor.up()) {
-                other = mOtherViewCalendar;
-                other.set(mViewCalendar);
-                other.month -= 1;
-                other.monthDay = mCursor.getSelectedDayOfMonth();
-
-                // restore the calendar cursor for the animation
-                mCursor.down();
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_DOWN:
-            if (mCursor.down()) {
-                other = mOtherViewCalendar;
-                other.set(mViewCalendar);
-                other.month += 1;
-                other.monthDay = mCursor.getSelectedDayOfMonth();
-
-                // restore the calendar cursor for the animation
-                mCursor.up();
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_LEFT:
-            if (mCursor.left()) {
-                other = mOtherViewCalendar;
-                other.set(mViewCalendar);
-                other.month -= 1;
-                other.monthDay = mCursor.getSelectedDayOfMonth();
-
-                // restore the calendar cursor for the animation
-                mCursor.right();
-            }
-            redraw = true;
-            break;
-
-        case KeyEvent.KEYCODE_DPAD_RIGHT:
-            if (mCursor.right()) {
-                other = mOtherViewCalendar;
-                other.set(mViewCalendar);
-                other.month += 1;
-                other.monthDay = mCursor.getSelectedDayOfMonth();
-
-                // restore the calendar cursor for the animation
-                mCursor.left();
-            }
-            redraw = true;
-            break;
-        }
-
-        if (other != null) {
-            other.normalize(true /* ignore DST */);
-            mNavigator.goTo(other, true);
-        } else if (redraw) {
-            mRedrawScreen = true;
-            invalidate();
-        }
-
-        return redraw;
-    }
-
-    class DismissPopup implements Runnable {
-        public void run() {
-            mPopup.dismiss();
-        }
-    }
-
-    // This is called when the activity is paused so that the popup can
-    // be dismissed.
-    void dismissPopup() {
-        if (!mShowToast) {
-            return;
-        }
-
-        // Protect against null-pointer exceptions
-        if (mPopup != null) {
-            mPopup.dismiss();
-        }
-
-        Handler handler = getHandler();
-        if (handler != null) {
-            handler.removeCallbacks(mDismissPopup);
-        }
-    }
-
-    public void updateView() {
-        mUpdateTZ.run();
-    }
-}
diff --git a/src/com/android/calendar/SearchActivity.java b/src/com/android/calendar/SearchActivity.java
new file mode 100644
index 0000000..579f0cc
--- /dev/null
+++ b/src/com/android/calendar/SearchActivity.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2010 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.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+import static android.provider.Calendar.EVENT_END_TIME;
+
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.CalendarController.ViewType;
+import com.android.calendar.agenda.AgendaFragment;
+
+import dalvik.system.VMRuntime;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.app.SearchManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Calendar.Events;
+import android.provider.SearchRecentSuggestions;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.SearchView;
+
+public class SearchActivity extends Activity
+        implements CalendarController.EventHandler, SearchView.OnQueryChangeListener {
+
+    private static final String TAG = SearchActivity.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    private static final int HANDLER_KEY = 0;
+
+    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
+
+    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+
+    protected static final String BUNDLE_KEY_RESTORE_SEARCH_QUERY =
+        "key_restore_search_query";
+
+    private static boolean mIsMultipane;
+
+    private CalendarController mController;
+
+    private EventInfoFragment mEventInfoFragment;
+
+    private long mCurrentEventId = -1;
+
+    private DeleteEventHelper mDeleteEventHelper;
+
+    private ContentResolver mContentResolver;
+
+    private ContentObserver mObserver = new ContentObserver(new Handler()) {
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            eventsChanged();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        // This needs to be created before setContentView
+        mController = CalendarController.getInstance(this);
+
+        mIsMultipane = (getResources().getConfiguration().screenLayout &
+                Configuration.SCREENLAYOUT_SIZE_XLARGE) != 0;
+
+        setContentView(R.layout.search);
+
+        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+        // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
+        // TODO: We should restore the old heap size once the activity reaches the idle state
+        VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
+
+        mContentResolver = getContentResolver();
+
+        // Must be the first to register because this activity can modify the
+        // list of event handlers in it's handle method. This affects who the
+        // rest of the handlers the controller dispatches to are.
+        mController.registerEventHandler(HANDLER_KEY, this);
+
+        mDeleteEventHelper = new DeleteEventHelper(this, this,
+                false /* don't exit when done */);
+
+        long millis = 0;
+        if (icicle != null) {
+            // Returns 0 if key not found
+            millis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME);
+            if (DEBUG) {
+                Log.v(TAG, "Restore value from icicle: " + millis);
+            }
+        }
+        if (millis == 0) {
+            // Didn't find a time in the bundle, look in intent or current time
+            millis = Utils.timeFromIntentInMillis(getIntent());
+        }
+
+        Intent intent = getIntent();
+        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+            String query = intent.getStringExtra(SearchManager.QUERY);
+            initFragments(millis, query);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        CalendarController.removeInstance(this);
+    }
+
+    private void initFragments(long timeMillis, String query) {
+        FragmentManager fragmentManager = getFragmentManager();
+        FragmentTransaction ft = fragmentManager.openTransaction();
+
+        AgendaFragment searchResultsFragment = new AgendaFragment(timeMillis);
+        ft.replace(R.id.search_results, searchResultsFragment);
+        mController.registerEventHandler(R.id.search_results, searchResultsFragment);
+        if (!mIsMultipane) {
+            findViewById(R.id.event_info).setVisibility(View.GONE);
+        }
+
+        ft.commit();
+        Time t = new Time();
+        t.set(timeMillis);
+        search(query, t);
+    }
+
+    private void showEventInfo(long eventId, long startMillis, long endMillis) {
+        if (mIsMultipane) {
+            FragmentManager fragmentManager = getFragmentManager();
+            FragmentTransaction ft = fragmentManager.openTransaction();
+
+            mEventInfoFragment =
+                new EventInfoFragment(eventId, startMillis, endMillis);
+            ft.replace(R.id.event_info, mEventInfoFragment);
+            ft.commit();
+            mController.registerEventHandler(R.id.event_info, mEventInfoFragment);
+        } else {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+            intent.setData(eventUri);
+            intent.setClassName(this, EventInfoActivity.class.getName());
+            intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+            intent.putExtra(EVENT_END_TIME, endMillis);
+            startActivity(intent);
+        }
+        mCurrentEventId = eventId;
+    }
+
+    private void search(String searchQuery, Time goToTime) {
+        // save query in recent queries
+        SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
+                CalendarRecentSuggestionsProvider.AUTHORITY,
+                CalendarRecentSuggestionsProvider.MODE);
+        suggestions.saveRecentQuery(searchQuery, null);
+
+
+        EventInfo searchEventInfo = new EventInfo();
+        searchEventInfo.eventType = EventType.SEARCH;
+        searchEventInfo.query = searchQuery;
+        searchEventInfo.viewType = ViewType.AGENDA;
+        if (goToTime != null) {
+            searchEventInfo.startTime = goToTime;
+        }
+        mController.sendEvent(this, searchEventInfo);
+    }
+
+    private void deleteEvent(long eventId, long startMillis, long endMillis) {
+        mDeleteEventHelper.delete(startMillis, endMillis, eventId, -1);
+        if (mIsMultipane && mEventInfoFragment != null
+                && eventId == mCurrentEventId) {
+            FragmentManager fragmentManager = getFragmentManager();
+            FragmentTransaction ft = fragmentManager.openTransaction();
+            ft.remove(mEventInfoFragment);
+            ft.commit();
+            mEventInfoFragment = null;
+            mController.deregisterEventHandler(R.id.event_info);
+            mCurrentEventId = -1;
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.search_title_bar, menu);
+        SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+        searchView.setIconifiedByDefault(true);
+        searchView.setOnQueryChangeListener(this);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        Time t = null;
+        switch (item.getItemId()) {
+            case R.id.action_today:
+                t = new Time();
+                t.setToNow();
+                mController.sendEvent(this, EventType.GO_TO, t, null, -1, ViewType.CURRENT);
+                return true;
+            case R.id.action_search:
+                onSearchRequested();
+                return true;
+            case R.id.action_settings:
+                mController.sendEvent(this, EventType.LAUNCH_SETTINGS, null, null, 0, 0);
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        // From the Android Dev Guide: "It's important to note that when
+        // onNewIntent(Intent) is called, the Activity has not been restarted,
+        // so the getIntent() method will still return the Intent that was first
+        // received with onCreate(). This is why setIntent(Intent) is called
+        // inside onNewIntent(Intent) (just in case you call getIntent() at a
+        // later time)."
+        setIntent(intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+            String query = intent.getStringExtra(SearchManager.QUERY);
+            search(query, null);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putLong(BUNDLE_KEY_RESTORE_TIME, mController.getTime());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
+        // We call this in case the user changed the time zone
+        eventsChanged();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mContentResolver.unregisterContentObserver(mObserver);
+    }
+
+//    @Override
+//    public boolean onKeyDown(int keyCode, KeyEvent event) {
+//        switch (keyCode) {
+//            case KeyEvent.KEYCODE_DEL:
+//                // Delete the currently selected event (if any)
+//                mAgendaListView.deleteSelectedEvent();
+//                break;
+//        }
+//        return super.onKeyDown(keyCode, event);
+//    }
+
+    @Override
+    public void goToToday() {
+    }
+
+    @Override
+    public void goTo(Time time, boolean animate) {
+    }
+
+    @Override
+    public long getSelectedTime() {
+        return 0;
+    }
+
+    @Override
+    public boolean getAllDay() {
+        return false;
+    }
+
+    @Override
+    public void eventsChanged() {
+        mController.sendEvent(this, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT);
+    }
+
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.VIEW_EVENT | EventType.DELETE_EVENT;
+    }
+
+    @Override
+    public void handleEvent(EventInfo event) {
+        long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false);
+        if (event.eventType == EventType.VIEW_EVENT) {
+            showEventInfo(event.id, event.startTime.toMillis(false), endTime);
+        } else if (event.eventType == EventType.DELETE_EVENT) {
+            deleteEvent(event.id, event.startTime.toMillis(false), endTime);
+        }
+    }
+
+    @Override
+    public boolean onQueryTextChanged(String newText) {
+        return false;
+    }
+
+    @Override
+    public boolean onSubmitQuery(String query) {
+        mController.sendEvent(
+                this, EventType.SEARCH, null, null, -1,
+                ViewType.CURRENT, query, getComponentName());
+        return false;
+    }
+}
diff --git a/src/com/android/calendar/SelectCalendarsActivity.java b/src/com/android/calendar/SelectCalendarsActivity.java
deleted file mode 100644
index 9b1b482..0000000
--- a/src/com/android/calendar/SelectCalendarsActivity.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import android.app.ExpandableListActivity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.os.Bundle;
-import android.provider.Calendar.Calendars;
-import android.view.View;
-import android.view.Window;
-import android.widget.AdapterView;
-import android.widget.ExpandableListView;
-
-
-public class SelectCalendarsActivity extends ExpandableListActivity
-    implements AdapterView.OnItemClickListener, View.OnClickListener {
-
-    private static final String TAG = "Calendar";
-    private static final String EXPANDED_KEY = "is_expanded";
-    private static final String ACCOUNT_UNIQUE_KEY = "ACCOUNT_KEY";
-    private View mView = null;
-    private Cursor mCursor = null;
-    private ExpandableListView mList;
-    private SelectCalendarsAdapter mAdapter;
-    private static final String[] PROJECTION = new String[] {
-        Calendars._ID,
-        Calendars._SYNC_ACCOUNT_TYPE,
-        Calendars._SYNC_ACCOUNT,
-        Calendars._SYNC_ACCOUNT_TYPE + " || " + Calendars._SYNC_ACCOUNT + " AS " +
-                ACCOUNT_UNIQUE_KEY,
-    };
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-        setContentView(R.layout.calendars_activity);
-        getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
-                Window.PROGRESS_INDETERMINATE_ON);
-        mList = getExpandableListView();
-        mView = findViewById(R.id.calendars);
-        Context context = mView.getContext();
-        //TODO Move managedQuery into a background thread.
-        //TODO change to something that supports group by queries.
-        mCursor = managedQuery(Calendars.CONTENT_URI, PROJECTION,
-                "1) GROUP BY (" + ACCOUNT_UNIQUE_KEY, //Cheap hack to make WHERE a GROUP BY query
-                null /* selectionArgs */,
-                Calendars._SYNC_ACCOUNT /*sort order*/);
-        MatrixCursor accountsCursor = Utils.matrixCursorFromCursor(mCursor);
-        startManagingCursor(accountsCursor);
-        mAdapter = new SelectCalendarsAdapter(context, accountsCursor, this);
-        mList.setAdapter(mAdapter);
-
-        mList.setOnChildClickListener(this);
-
-        findViewById(R.id.btn_done).setOnClickListener(this);
-        findViewById(R.id.btn_discard).setOnClickListener(this);
-
-        // Start a background sync to get the list of calendars from the server.
-        startCalendarMetafeedSync();
-        int count = mList.getCount();
-        for(int i = 0; i < count; i++) {
-            mList.expandGroup(i);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mAdapter.startRefreshStopDelay();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mAdapter.cancelRefreshStopDelay();
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        boolean[] isExpanded;
-        mList = getExpandableListView();
-        if(mList != null) {
-            int count = mList.getCount();
-            isExpanded = new boolean[count];
-            for(int i = 0; i < count; i++) {
-                isExpanded[i] = mList.isGroupExpanded(i);
-            }
-        } else {
-            isExpanded = null;
-        }
-        outState.putBooleanArray(EXPANDED_KEY, isExpanded);
-        //TODO Store this to preferences instead so it remains on restart
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Bundle state) {
-        super.onRestoreInstanceState(state);
-        mList = getExpandableListView();
-        boolean[] isExpanded = state.getBooleanArray(EXPANDED_KEY);
-        if(mList != null && isExpanded != null && mList.getCount() >= isExpanded.length) {
-            for(int i = 0; i < isExpanded.length; i++) {
-                if(isExpanded[i] && !mList.isGroupExpanded(i)) {
-                    mList.expandGroup(i);
-                } else if(!isExpanded[i] && mList.isGroupExpanded(i)){
-                    mList.collapseGroup(i);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean onChildClick(ExpandableListView parent, View view, int groupPosition,
-            int childPosition, long id) {
-        MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
-        return button.performClick();
-    }
-
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
-        button.performClick();
-    }
-
-    /** {@inheritDoc} */
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.btn_done: {
-                doSaveAction();
-                break;
-            }
-            case R.id.btn_discard: {
-                finish();
-                break;
-            }
-        }
-    }
-
-    /*TODO*/
-    private void doSaveAction() {
-        mAdapter.doSaveAction();
-        finish();
-    }
-
-    // startCalendarMetafeedSync() checks the server for an updated list of
-    // Calendars (in the background).
-    //
-    // If a Calendar is added on the web (and it is selected and not
-    // hidden) then it will be added to the list of calendars on the phone
-    // (when this finishes).  When a new calendar from the
-    // web is added to the phone, then the events for that calendar are also
-    // downloaded from the web.
-    //
-    // This sync is done automatically in the background when the
-    // SelectCalendars activity is started.
-    private void startCalendarMetafeedSync() {
-        Bundle extras = new Bundle();
-        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
-        extras.putBoolean("metafeedonly", true);
-        ContentResolver.requestSync(null /* all accounts */,
-                Calendars.CONTENT_URI.getAuthority(), extras);
-    }
-}
diff --git a/src/com/android/calendar/SelectCalendarsAdapter.java b/src/com/android/calendar/SelectCalendarsAdapter.java
deleted file mode 100644
index c2c21ac..0000000
--- a/src/com/android/calendar/SelectCalendarsAdapter.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2007 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.calendar;
-
-import android.accounts.AccountManager;
-import android.accounts.AuthenticatorDescription;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.provider.Calendar.Calendars;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorTreeAdapter;
-import android.widget.TextView;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-public class SelectCalendarsAdapter extends CursorTreeAdapter implements View.OnClickListener {
-
-    private static final String TAG = "Calendar";
-
-    private static final String COLLATE_NOCASE = " COLLATE NOCASE";
-    private static final String IS_PRIMARY = "\"primary\"";
-    private static final String CALENDARS_ORDERBY = IS_PRIMARY + " DESC," + Calendars.DISPLAY_NAME +
-            COLLATE_NOCASE;
-    private static final String ACCOUNT_SELECTION = Calendars._SYNC_ACCOUNT + "=?"
-            + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
-
-    // The drawables used for the button to change the visible and sync states on a calendar
-    private static final int[] SYNC_VIS_BUTTON_RES = new int[] {
-        R.drawable.widget_show,
-        R.drawable.widget_sync,
-        R.drawable.widget_off
-    };
-
-    private final LayoutInflater mInflater;
-    private final ContentResolver mResolver;
-    private final SelectCalendarsActivity mActivity;
-    private final View mView;
-    private final static Runnable mStopRefreshing = new Runnable() {
-        public void run() {
-            mRefresh = false;
-        }
-    };
-    private Map<String, AuthenticatorDescription> mTypeToAuthDescription
-        = new HashMap<String, AuthenticatorDescription>();
-    protected AuthenticatorDescription[] mAuthDescs;
-
-    // These track changes to the visible (selected) and synced state of calendars
-    private Map<Long, Boolean[]> mCalendarChanges
-        = new HashMap<Long, Boolean[]>();
-    private Map<Long, Boolean[]> mCalendarInitialStates
-        = new HashMap<Long, Boolean[]>();
-    private static final int SELECTED_INDEX = 0;
-    private static final int SYNCED_INDEX = 1;
-    private static final int CHANGES_SIZE = 2;
-
-    // This is for keeping MatrixCursor copies so that we can requery in the background.
-    private static Map<String, Cursor> mChildrenCursors
-        = new HashMap<String, Cursor>();
-
-    private static AsyncCalendarsUpdater mCalendarsUpdater;
-    // This is to keep our update tokens separate from other tokens. Since we cancel old updates
-    // when a new update comes in, we'd like to leave a token space that won't be canceled.
-    private static final int MIN_UPDATE_TOKEN = 1000;
-    private static int mUpdateToken = MIN_UPDATE_TOKEN;
-    // How long to wait between requeries of the calendars to see if anything has changed.
-    private static final int REFRESH_DELAY = 5000;
-    // How long to keep refreshing for
-    private static final int REFRESH_DURATION = 60000;
-    private static boolean mRefresh = true;
-    private int mNumAccounts;
-
-    private static String syncedVisible;
-    private static String syncedNotVisible;
-    private static String notSyncedNotVisible;
-
-    // This is to keep track of whether or not multiple calendars have the same display name
-    private static HashMap<String, Boolean> mIsDuplicateName = new HashMap<String, Boolean>();
-
-    private static final String[] PROJECTION = new String[] {
-      Calendars._ID,
-      Calendars._SYNC_ACCOUNT,
-      Calendars.OWNER_ACCOUNT,
-      Calendars.DISPLAY_NAME,
-      Calendars.COLOR,
-      Calendars.SELECTED,
-      Calendars.SYNC_EVENTS,
-      "(" + Calendars._SYNC_ACCOUNT + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY,
-    };
-    //Keep these in sync with the projection
-    private static final int ID_COLUMN = 0;
-    private static final int ACCOUNT_COLUMN = 1;
-    private static final int OWNER_COLUMN = 2;
-    private static final int NAME_COLUMN = 3;
-    private static final int COLOR_COLUMN = 4;
-    private static final int SELECTED_COLUMN = 5;
-    private static final int SYNCED_COLUMN = 6;
-    private static final int PRIMARY_COLUMN = 7;
-
-    private class AsyncCalendarsUpdater extends AsyncQueryHandler {
-
-        public AsyncCalendarsUpdater(ContentResolver cr) {
-            super(cr);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if(cursor == null) {
-                return;
-            }
-
-            Cursor currentCursor = mChildrenCursors.get(cookie);
-            // Check if the new cursor has the same content as our old cursor
-            if (currentCursor != null) {
-                if (Utils.compareCursors(currentCursor, cursor)) {
-                    cursor.close();
-                    return;
-                }
-            }
-            // If not then make a new matrix cursor for our Map
-            MatrixCursor newCursor = Utils.matrixCursorFromCursor(cursor);
-            cursor.close();
-            // And update our list of duplicated names
-            Utils.checkForDuplicateNames(mIsDuplicateName, newCursor, NAME_COLUMN);
-
-            mChildrenCursors.put((String)cookie, newCursor);
-            try {
-                setChildrenCursor(token, newCursor);
-                mActivity.startManagingCursor(newCursor);
-            } catch (NullPointerException e) {
-                Log.w(TAG, "Adapter expired, try again on the next query: " + e);
-            }
-            // Clean up our old cursor if we had one. We have to do this after setting the new
-            // cursor so that our view doesn't throw on an invalid cursor.
-            if (currentCursor != null) {
-                mActivity.stopManagingCursor(currentCursor);
-                currentCursor.close();
-            }
-        }
-    }
-
-
-
-    /**
-     * Method for changing the sync/vis state when a calendar's button is pressed.
-     *
-     * This gets called when the MultiStateButton for a calendar is clicked. It cycles the sync/vis
-     * state for the associated calendar and saves a change of state to a hashmap. It also compares
-     * against the original value and removes any changes from the hashmap if this is back
-     * at its initial state.
-     */
-    public void onClick(View v) {
-        View view = (View)v.getTag();
-        long id = (Long)view.getTag();
-        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
-        String status = syncedNotVisible;
-        Boolean[] change;
-        Boolean[] initialState = mCalendarInitialStates.get(id);
-        if (mCalendarChanges.containsKey(id)) {
-            change = mCalendarChanges.get(id);
-        } else {
-            change = new Boolean[CHANGES_SIZE];
-            change[SELECTED_INDEX] = initialState[SELECTED_INDEX];
-            change[SYNCED_INDEX] = initialState[SYNCED_INDEX];
-            mCalendarChanges.put(id, change);
-        }
-
-        if (change[SELECTED_INDEX]) {
-            change[SELECTED_INDEX] = false;
-            status = syncedNotVisible;
-        }
-        else if (change[SYNCED_INDEX]) {
-            change[SYNCED_INDEX] = false;
-            status = notSyncedNotVisible;
-        }
-        else
-        {
-            change[SYNCED_INDEX] = true;
-            change[SELECTED_INDEX] = true;
-            status = syncedVisible;
-        }
-        setText(view, R.id.status, status);
-        if (change[SELECTED_INDEX] == initialState[SELECTED_INDEX] &&
-                change[SYNCED_INDEX] == initialState[SYNCED_INDEX]) {
-            mCalendarChanges.remove(id);
-        }
-    }
-
-    public SelectCalendarsAdapter(Context context, Cursor cursor, SelectCalendarsActivity act) {
-        super(cursor, context);
-        syncedVisible = context.getString(R.string.synced_visible);
-        syncedNotVisible = context.getString(R.string.synced_not_visible);
-        notSyncedNotVisible = context.getString(R.string.not_synced_not_visible);
-
-        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mResolver = context.getContentResolver();
-        mActivity = act;
-        if (mCalendarsUpdater == null) {
-            mCalendarsUpdater = new AsyncCalendarsUpdater(mResolver);
-        }
-
-        mNumAccounts = cursor.getCount();
-        if(mNumAccounts == 0) {
-            //Should never happen since Calendar requires an account exist to use it.
-            Log.e(TAG, "SelectCalendarsAdapter: No accounts were returned!");
-        }
-        //Collect proper description for account types
-        mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
-        for (int i = 0; i < mAuthDescs.length; i++) {
-            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
-        }
-        mView = mActivity.getExpandableListView();
-        mRefresh = true;
-    }
-
-    public void startRefreshStopDelay() {
-        mRefresh = true;
-        mView.postDelayed(mStopRefreshing, REFRESH_DURATION);
-    }
-
-    public void cancelRefreshStopDelay() {
-        mView.removeCallbacks(mStopRefreshing);
-    }
-
-    /*
-     * Write back the changes that have been made. The sync code will pick up any changes and
-     * do updates on its own.
-     */
-    public void doSaveAction() {
-        // Cancel the previous operation
-        mCalendarsUpdater.cancelOperation(mUpdateToken);
-        mUpdateToken++;
-        // This is to allow us to do queries and updates with the same AsyncQueryHandler without
-        // accidently canceling queries.
-        if(mUpdateToken < MIN_UPDATE_TOKEN) mUpdateToken = MIN_UPDATE_TOKEN;
-
-        Iterator<Long> changeKeys = mCalendarChanges.keySet().iterator();
-        while (changeKeys.hasNext()) {
-            long id = changeKeys.next();
-            Boolean[] change = mCalendarChanges.get(id);
-            int newSelected = change[SELECTED_INDEX] ? 1 : 0;
-            int newSynced = change[SYNCED_INDEX] ? 1 : 0;
-
-            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
-            ContentValues values = new ContentValues();
-            values.put(Calendars.SELECTED, newSelected);
-            values.put(Calendars.SYNC_EVENTS, newSynced);
-            mCalendarsUpdater.startUpdate(mUpdateToken, id, uri, values, null, null);
-        }
-    }
-
-    private static void setText(View view, int id, String text) {
-        if (TextUtils.isEmpty(text)) {
-            return;
-        }
-        TextView textView = (TextView) view.findViewById(id);
-        textView.setText(text);
-    }
-
-    /**
-     * Gets the label associated with a particular account type. If none found, return null.
-     * @param accountType the type of account
-     * @return a CharSequence for the label or null if one cannot be found.
-     */
-    protected CharSequence getLabelForType(final String accountType) {
-        CharSequence label = null;
-        if (mTypeToAuthDescription.containsKey(accountType)) {
-             try {
-                 AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
-                 Context authContext = mActivity.createPackageContext(desc.packageName, 0);
-                 label = authContext.getResources().getText(desc.labelId);
-             } catch (PackageManager.NameNotFoundException e) {
-                 Log.w(TAG, "No label for account type " + ", type " + accountType);
-             }
-        }
-        return label;
-    }
-
-    @Override
-    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
-        String account = cursor.getString(ACCOUNT_COLUMN);
-        String status = notSyncedNotVisible;
-        int state = 2;
-        int position = cursor.getPosition();
-        long id = cursor.getLong(ID_COLUMN);
-
-        // First see if the user has already changed the state of this calendar
-        Boolean[] initialState = mCalendarChanges.get(id);
-        // if we haven't already started making changes update the initial state in case it changed
-        if (initialState == null) {
-            initialState = new Boolean[CHANGES_SIZE];
-            initialState[SELECTED_INDEX] = cursor.getInt(SELECTED_COLUMN) == 1;
-            initialState[SYNCED_INDEX] = cursor.getInt(SYNCED_COLUMN) == 1;
-            mCalendarInitialStates.put(id, initialState);
-        }
-
-        if(initialState[SYNCED_INDEX]) {
-            if(initialState[SELECTED_INDEX]) {
-                status = syncedVisible;
-                state = 0;
-            } else {
-                status = syncedNotVisible;
-                state = 1;
-            }
-        }
-
-        view.findViewById(R.id.color)
-            .setBackgroundDrawable(Utils.getColorChip(cursor.getInt(COLOR_COLUMN)));
-        String name = cursor.getString(NAME_COLUMN);
-        String owner = cursor.getString(OWNER_COLUMN);
-        if (mIsDuplicateName.containsKey(name) && mIsDuplicateName.get(name) &&
-                !name.equalsIgnoreCase(owner)) {
-            name = new StringBuilder(name)
-                    .append(Utils.OPEN_EMAIL_MARKER)
-                    .append(owner)
-                    .append(Utils.CLOSE_EMAIL_MARKER)
-                    .toString();
-        }
-        setText(view, R.id.calendar, name);
-        setText(view, R.id.status, status);
-        MultiStateButton button = (MultiStateButton) view.findViewById(R.id.multiStateButton);
-
-        //Set up the listeners so a click on the button will change the state.
-        //The view already uses the onChildClick method in the activity.
-        button.setTag(view);
-        view.setTag(id);
-        button.setOnClickListener(this);
-        button.setButtonResources(SYNC_VIS_BUTTON_RES);
-        button.setState(state);
-    }
-
-    @Override
-    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
-        int accountColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
-        int accountTypeColumn = cursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT_TYPE);
-        String account = cursor.getString(accountColumn);
-        String accountType = cursor.getString(accountTypeColumn);
-        setText(view, R.id.account, account);
-        setText(view, R.id.account_type, getLabelForType(accountType).toString());
-    }
-
-    @Override
-    protected Cursor getChildrenCursor(Cursor groupCursor) {
-        int accountColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT);
-        int accountTypeColumn = groupCursor.getColumnIndexOrThrow(Calendars._SYNC_ACCOUNT_TYPE);
-        String account = groupCursor.getString(accountColumn);
-        String accountType = groupCursor.getString(accountTypeColumn);
-        //Get all the calendars for just this account.
-        Cursor childCursor = mChildrenCursors.get(account);
-        new RefreshCalendars(groupCursor.getPosition(), account, accountType).run();
-        return childCursor;
-    }
-
-    @Override
-    protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
-            ViewGroup parent) {
-        return mInflater.inflate(R.layout.calendar_item, parent, false);
-    }
-
-    @Override
-    protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
-            ViewGroup parent) {
-        return mInflater.inflate(R.layout.account_item, parent, false);
-    }
-
-    private class RefreshCalendars implements Runnable {
-
-        int mToken;
-        String mAccount;
-        String mAccountType;
-
-        public RefreshCalendars(int token, String cookie, String accountType) {
-            mToken = token;
-            mAccount = cookie;
-            mAccountType = accountType;
-        }
-
-        public void run() {
-            mCalendarsUpdater.cancelOperation(mToken);
-            // Set up a refresh for some point in the future if we haven't stopped updates yet
-            if(mRefresh) {
-                mView.postDelayed(new RefreshCalendars(mToken, mAccount, mAccountType),
-                        REFRESH_DELAY);
-            }
-            mCalendarsUpdater.startQuery(mToken,
-                    mAccount,
-                    Calendars.CONTENT_URI, PROJECTION,
-                    ACCOUNT_SELECTION,
-                    new String[] { mAccount, mAccountType } /*selectionArgs*/,
-                    CALENDARS_ORDERBY);
-        }
-    }
-}
diff --git a/src/com/android/calendar/TimezoneAdapter.java b/src/com/android/calendar/TimezoneAdapter.java
index f59885c..3890ebb 100644
--- a/src/com/android/calendar/TimezoneAdapter.java
+++ b/src/com/android/calendar/TimezoneAdapter.java
@@ -235,7 +235,7 @@
         ids.add(TimeZone.getDefault().getID());
 
         // add in recent timezone selections
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(mContext);
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mContext);
         String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
         if (recentsString != null) {
             String[] recents = recentsString.split(RECENT_TIMEZONES_DELIMITER);
@@ -303,7 +303,7 @@
      * @see {@link #MAX_RECENT_TIMEZONES}
      */
     public void saveRecentTimezone(String id) {
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(mContext);
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mContext);
         String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
         List<String> recents;
         if (recentsString == null) {
@@ -318,15 +318,13 @@
         }
         recents.add(id);
         recentsString = Utils.join(recents, RECENT_TIMEZONES_DELIMITER);
-        prefs.edit().putString(KEY_RECENT_TIMEZONES, recentsString).apply();
+        Utils.setSharedPreference(mContext, KEY_RECENT_TIMEZONES, recentsString);
     }
 
     /**
-     * Returns an array of ids/time zones.
-     *
-     * This returns a double indexed array of ids and time zones
-     * for Calendar. It is an inefficient method and shouldn't be
-     * called often, but can be used for one time generation of
+     * Returns an array of ids/time zones. This returns a double indexed array
+     * of ids and time zones for Calendar. It is an inefficient method and
+     * shouldn't be called often, but can be used for one time generation of
      * this list.
      *
      * @return double array of tz ids and tz names
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
index 4a3a37f..76b9d66 100644
--- a/src/com/android/calendar/Utils.java
+++ b/src/com/android/calendar/Utils.java
@@ -18,6 +18,9 @@
 
 import static android.provider.Calendar.EVENT_BEGIN_TIME;
 
+import com.android.calendar.CalendarController.ViewType;
+
+import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -26,21 +29,39 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.net.Uri;
+import android.os.Bundle;
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.CalendarUtils.TimeZoneUtils;
 import android.util.Log;
-import android.view.animation.AlphaAnimation;
-import android.widget.ViewFlipper;
 
 import java.util.Calendar;
 import java.util.Formatter;
 import java.util.List;
 import java.util.Map;
+import java.util.TimeZone;
 
 public class Utils {
     private static final boolean DEBUG = true;
     private static final String TAG = "CalUtils";
+    // Set to 0 until we have UI to perform undo
+    public static final long UNDO_DELAY = 0;
+
+    // For recurring events which instances of the series are being modified
+    public static final int MODIFY_UNINITIALIZED = 0;
+    public static final int MODIFY_SELECTED = 1;
+    public static final int MODIFY_ALL_FOLLOWING = 2;
+    public static final int MODIFY_ALL = 3;
+
+    // When the edit event view finishes it passes back the appropriate exit
+    // code.
+    public static final int DONE_REVERT = 1 << 0;
+    public static final int DONE_SAVE = 1 << 1;
+    public static final int DONE_DELETE = 1 << 2;
+    // And should re run with DONE_EXIT if it should also leave the view, just
+    // exiting is identical to reverting
+    public static final int DONE_EXIT = 1 << 0;
+
     private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF;
     private static final int HIGH_ALPHA = 255 << 24;
     private static final int MED_ALPHA = 180 << 24;
@@ -49,35 +70,49 @@
     protected static final String OPEN_EMAIL_MARKER = " <";
     protected static final String CLOSE_EMAIL_MARKER = ">";
     /* The corner should be rounded on the top right and bottom right */
-    private static final float[] CORNERS = new float[] {0, 0, 5, 5, 5, 5, 0, 0};
+    private static final float[] CORNERS = new float[] { 0, 0, 5, 5, 5, 5, 0, 0 };
 
-    // The name of the shared preferences file. This name must be maintained for historical
-    // reasons, as it's what PreferenceManager assigned the first time the file was created.
+    public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW";
+    public static final String INTENT_KEY_VIEW_TYPE = "VIEW";
+    public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY";
+
+    // The name of the shared preferences file. This name must be maintained for
+    // historical
+    // reasons, as it's what PreferenceManager assigned the first time the file
+    // was created.
     private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
 
     private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME);
 
-    public static void startActivity(Context context, String className, long time) {
-        Intent intent = new Intent(Intent.ACTION_VIEW);
+    public static int getViewTypeFromIntentAndSharedPref(Activity activity) {
+        Intent intent = activity.getIntent();
+        Bundle extras = intent.getExtras();
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(activity);
 
-        intent.setClassName(context, className);
-        intent.putExtra(EVENT_BEGIN_TIME, time);
-        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        if (TextUtils.equals(intent.getAction(), Intent.ACTION_EDIT)) {
+            return ViewType.EDIT;
+        }
+        if (extras != null) {
+            if (extras.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) {
+                // This is the "detail" view which is either agenda or day view
+                return prefs.getInt(GeneralPreferences.KEY_DETAILED_VIEW,
+                        GeneralPreferences.DEFAULT_DETAILED_VIEW);
+            } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras.getString(INTENT_KEY_VIEW_TYPE))) {
+                // Not sure who uses this. This logic came from LaunchActivity
+                return ViewType.DAY;
+            }
+        }
 
-        context.startActivity(intent);
-    }
-
-    static String getSharedPreference(Context context, String key, String defaultValue) {
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context);
-        return prefs.getString(key, defaultValue);
+        // Default to the last view
+        return prefs.getInt(
+                GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW);
     }
 
     /**
-     * Writes a new home time zone to the db.
-     *
-     * Updates the home time zone in the db asynchronously and updates
-     * the local cache. Sending a time zone of **tbd** will cause it to
-     * be set to the device's time zone. null or empty tz will be ignored.
+     * Writes a new home time zone to the db. Updates the home time zone in the
+     * db asynchronously and updates the local cache. Sending a time zone of
+     * **tbd** will cause it to be set to the device's time zone. null or empty
+     * tz will be ignored.
      *
      * @param context The calling activity
      * @param timeZone The time zone to set Calendar to, or **tbd**
@@ -87,18 +122,19 @@
     }
 
     /**
-     * Gets the time zone that Calendar should be displayed in
-     *
-     * This is a helper method to get the appropriate time zone for Calendar. If this
-     * is the first time this method has been called it will initiate an asynchronous
-     * query to verify that the data in preferences is correct. The callback supplied
-     * will only be called if this query returns a value other than what is stored in
-     * preferences and should cause the calling activity to refresh anything that
-     * depends on calling this method.
+     * Gets the time zone that Calendar should be displayed in This is a helper
+     * method to get the appropriate time zone for Calendar. If this is the
+     * first time this method has been called it will initiate an asynchronous
+     * query to verify that the data in preferences is correct. The callback
+     * supplied will only be called if this query returns a value other than
+     * what is stored in preferences and should cause the calling activity to
+     * refresh anything that depends on calling this method.
      *
      * @param context The calling activity
-     * @param callback The runnable that should execute if a query returns new values
-     * @return The string value representing the time zone Calendar should display
+     * @param callback The runnable that should execute if a query returns new
+     *            values
+     * @return The string value representing the time zone Calendar should
+     *         display
      */
     public static String getTimeZone(Context context, Runnable callback) {
         return mTZUtils.getTimeZone(context, callback);
@@ -110,44 +146,65 @@
      * @param context the context is required only if the time is shown
      * @param startMillis the start time in UTC milliseconds
      * @param endMillis the end time in UTC milliseconds
-     * @param flags a bit mask of options See
-     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+     * @param flags a bit mask of options See {@link #formatDateRange(Context,
+     *            Formatter, long, long, int, String) formatDateRange}
      * @return a string containing the formatted date/time range.
      */
-    public static String formatDateRange(Context context, long startMillis,
-            long endMillis, int flags) {
+    public static String formatDateRange(
+            Context context, long startMillis, long endMillis, int flags) {
         return mTZUtils.formatDateRange(context, startMillis, endMillis, flags);
     }
 
-    static void setSharedPreference(Context context, String key, String value) {
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context);
+    public static String getSharedPreference(Context context, String key, String defaultValue) {
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+        return prefs.getString(key, defaultValue);
+    }
+
+    public static int getSharedPreference(Context context, String key, int defaultValue) {
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+        return prefs.getInt(key, defaultValue);
+    }
+
+    /**
+     * Asynchronously sets the preference with the given key to the given value
+     *
+     * @param context the context to use to get preferences from
+     * @param key the key of the preference to set
+     * @param value the value to set
+     */
+    public static void setSharedPreference(Context context, String key, String value) {
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+        prefs.edit().putString(key, value).apply();
+    }
+
+    static void setSharedPreference(Context context, String key, boolean value) {
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
         SharedPreferences.Editor editor = prefs.edit();
-        editor.putString(key, value);
+        editor.putBoolean(key, value);
         editor.apply();
     }
 
+    /**
+     * Save default agenda/day/week/month view for next time
+     *
+     * @param context
+     * @param viewId {@link CalendarController.ViewType}
+     */
     static void setDefaultView(Context context, int viewId) {
-        String activityString = CalendarApplication.ACTIVITY_NAMES[viewId];
-
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context);
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
         SharedPreferences.Editor editor = prefs.edit();
-        if (viewId == CalendarApplication.AGENDA_VIEW_ID ||
-                viewId == CalendarApplication.DAY_VIEW_ID) {
+
+        if (viewId == CalendarController.ViewType.AGENDA
+                || viewId == CalendarController.ViewType.DAY) {
             // Record the (new) detail start view only for Agenda and Day
-            editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString);
+            editor.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId);
         }
 
         // Record the (new) start view
-        editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
+        editor.putInt(GeneralPreferences.KEY_START_VIEW, viewId);
         editor.apply();
     }
 
-    public static final Time timeFromIntent(Intent intent) {
-        Time time = new Time();
-        time.set(timeFromIntentInMillis(intent));
-        return time;
-    }
-
     public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
         MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames());
         int numColumns = cursor.getColumnCount();
@@ -165,11 +222,11 @@
     /**
      * Compares two cursors to see if they contain the same data.
      *
-     * @return Returns true of the cursors contain the same data and are not null, false
-     * otherwise
+     * @return Returns true of the cursors contain the same data and are not
+     *         null, false otherwise
      */
     public static boolean compareCursors(Cursor c1, Cursor c2) {
-        if(c1 == null || c2 == null) {
+        if (c1 == null || c2 == null) {
             return false;
         }
 
@@ -184,9 +241,9 @@
 
         c1.moveToPosition(-1);
         c2.moveToPosition(-1);
-        while(c1.moveToNext() && c2.moveToNext()) {
-            for(int i = 0; i < numColumns; i++) {
-                if(!TextUtils.equals(c1.getString(i), c2.getString(i))) {
+        while (c1.moveToNext() && c2.moveToNext()) {
+            for (int i = 0; i < numColumns; i++) {
+                if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
                     return false;
                 }
             }
@@ -200,17 +257,18 @@
      * then that time is returned. Otherwise, the current time is returned.
      */
     public static final long timeFromIntentInMillis(Intent intent) {
-        // If the time was specified, then use that.  Otherwise, use the current time.
+        // If the time was specified, then use that. Otherwise, use the current
+        // time.
         Uri data = intent.getData();
         long millis = intent.getLongExtra(EVENT_BEGIN_TIME, -1);
         if (millis == -1 && data != null && data.isHierarchical()) {
             List<String> path = data.getPathSegments();
-            if(path.size() == 2 && path.get(0).equals("time")) {
+            if (path.size() == 2 && path.get(0).equals("time")) {
                 try {
                     millis = Long.valueOf(data.getLastPathSegment());
                 } catch (NumberFormatException e) {
-                    Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time " +
-                            "found. Using current time.");
+                    Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time "
+                            + "found. Using current time.");
                 }
             }
         }
@@ -220,45 +278,28 @@
         return millis;
     }
 
-    public static final void applyAlphaAnimation(ViewFlipper v) {
-        AlphaAnimation in = new AlphaAnimation(0.0f, 1.0f);
-
-        in.setStartOffset(0);
-        in.setDuration(500);
-
-        AlphaAnimation out = new AlphaAnimation(1.0f, 0.0f);
-
-        out.setStartOffset(0);
-        out.setDuration(500);
-
-        v.setInAnimation(in);
-        v.setOutAnimation(out);
-    }
-
     public static Drawable getColorChip(int color) {
         /*
-         * We want the color chip to have a nice gradient using
-         * the color of the calendar. To do this we use a GradientDrawable.
-         * The color supplied has an alpha of FF so we first do:
-         * color & 0x00FFFFFF
-         * to clear the alpha. Then we add our alpha to it.
-         * We use 3 colors to get a step effect where it starts off very
-         * light and quickly becomes dark and then a slow transition to
-         * be even darker.
+         * We want the color chip to have a nice gradient using the color of the
+         * calendar. To do this we use a GradientDrawable. The color supplied
+         * has an alpha of FF so we first do: color & 0x00FFFFFF to clear the
+         * alpha. Then we add our alpha to it. We use 3 colors to get a step
+         * effect where it starts off very light and quickly becomes dark and
+         * then a slow transition to be even darker.
          */
         color &= CLEAR_ALPHA_MASK;
         int startColor = color | HIGH_ALPHA;
         int middleColor = color | MED_ALPHA;
         int endColor = color | LOW_ALPHA;
-        int[] colors = new int[] {startColor, middleColor, endColor};
+        int[] colors = new int[] { startColor, middleColor, endColor };
         GradientDrawable d = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors);
         d.setCornerRadii(CORNERS);
         return d;
     }
 
     /**
-     * Formats the given Time object so that it gives the month and year
-     * (for example, "September 2007").
+     * Formats the given Time object so that it gives the month and year (for
+     * example, "September 2007").
      *
      * @param time the time to format
      * @return the string containing the weekday and the date
@@ -267,18 +308,6 @@
         return time.format(context.getResources().getString(R.string.month_year));
     }
 
-    // TODO: replace this with the correct i18n way to do this
-    public static final String englishNthDay[] = {
-        "", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th",
-        "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th",
-        "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th",
-        "30th", "31st"
-    };
-
-    public static String formatNth(int nth) {
-        return "the " + englishNthDay[nth];
-    }
-
     /**
      * Returns a list joined together by the provided delimiter, for example,
      * ["a", "b", "c"] could be joined into "a,b,c"
@@ -302,21 +331,22 @@
     }
 
     /**
-     * Sets the time to the beginning of the day (midnight) by clearing the
-     * hour, minute, and second fields.
-     */
-    static void setTimeToStartOfDay(Time time) {
-        time.second = 0;
-        time.minute = 0;
-        time.hour = 0;
-    }
-
-    /**
      * Get first day of week as android.text.format.Time constant.
+     *
      * @return the first day of week in android.text.format.Time
      */
-    public static int getFirstDayOfWeek() {
-        int startDay = Calendar.getInstance().getFirstDayOfWeek();
+    public static int getFirstDayOfWeek(Context context) {
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+        String pref = prefs.getString(
+                GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT);
+
+        int startDay;
+        if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) {
+            startDay = Calendar.getInstance().getFirstDayOfWeek();
+        } else {
+            startDay = Integer.parseInt(pref);
+        }
+
         if (startDay == Calendar.SATURDAY) {
             return Time.SATURDAY;
         } else if (startDay == Calendar.MONDAY) {
@@ -328,39 +358,57 @@
 
     /**
      * Determine whether the column position is Saturday or not.
+     *
      * @param column the column position
      * @param firstDayOfWeek the first day of week in android.text.format.Time
      * @return true if the column is Saturday position
      */
     public static boolean isSaturday(int column, int firstDayOfWeek) {
         return (firstDayOfWeek == Time.SUNDAY && column == 6)
-            || (firstDayOfWeek == Time.MONDAY && column == 5)
-            || (firstDayOfWeek == Time.SATURDAY && column == 0);
+                || (firstDayOfWeek == Time.MONDAY && column == 5)
+                || (firstDayOfWeek == Time.SATURDAY && column == 0);
     }
 
     /**
      * Determine whether the column position is Sunday or not.
+     *
      * @param column the column position
      * @param firstDayOfWeek the first day of week in android.text.format.Time
      * @return true if the column is Sunday position
      */
     public static boolean isSunday(int column, int firstDayOfWeek) {
         return (firstDayOfWeek == Time.SUNDAY && column == 0)
-            || (firstDayOfWeek == Time.MONDAY && column == 6)
-            || (firstDayOfWeek == Time.SATURDAY && column == 1);
+                || (firstDayOfWeek == Time.MONDAY && column == 6)
+                || (firstDayOfWeek == Time.SATURDAY && column == 1);
+    }
+
+    /**
+     * Convert given UTC time into current local time.
+     *
+     * @param recycle Time object to recycle, otherwise null.
+     * @param utcTime Time to convert, in UTC.
+     */
+    public static long convertUtcToLocal(Time recycle, long utcTime) {
+        if (recycle == null) {
+            recycle = new Time();
+        }
+        recycle.timezone = Time.TIMEZONE_UTC;
+        recycle.set(utcTime);
+        recycle.timezone = TimeZone.getDefault().getID();
+        return recycle.normalize(true);
     }
 
     /**
      * Scan through a cursor of calendars and check if names are duplicated.
+     * This travels a cursor containing calendar display names and fills in the
+     * provided map with whether or not each name is repeated.
      *
-     * This travels a cursor containing calendar display names and fills in the provided map with
-     * whether or not each name is repeated.
      * @param isDuplicateName The map to put the duplicate check results in.
      * @param cursor The query of calendars to check
      * @param nameIndex The column of the query that contains the display name
      */
-    public static void checkForDuplicateNames(Map<String, Boolean> isDuplicateName, Cursor cursor,
-            int nameIndex) {
+    public static void checkForDuplicateNames(
+            Map<String, Boolean> isDuplicateName, Cursor cursor, int nameIndex) {
         isDuplicateName.clear();
         cursor.moveToPosition(-1);
         while (cursor.moveToNext()) {
@@ -371,4 +419,15 @@
             }
         }
     }
+
+    /**
+     * Null-safe object comparison
+     *
+     * @param s1
+     * @param s2
+     * @return
+     */
+    public static boolean equals(Object o1, Object o2) {
+        return o1 == null ? o2 == null : o1.equals(o2);
+    }
 }
diff --git a/src/com/android/calendar/WeekActivity.java b/src/com/android/calendar/WeekActivity.java
deleted file mode 100644
index 96b3bd7..0000000
--- a/src/com/android/calendar/WeekActivity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2006 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.calendar;
-
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.format.Time;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.ProgressBar;
-import android.widget.ViewSwitcher;
-
-public class WeekActivity extends CalendarActivity implements ViewSwitcher.ViewFactory {
-    /**
-     * The view id used for all the views we create. It's OK to have all child
-     * views have the same ID. This ID is used to pick which view receives
-     * focus when a view hierarchy is saved / restore
-     */
-    private static final int VIEW_ID = 1;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        setContentView(R.layout.week_activity);
-
-        mSelectedDay = Utils.timeFromIntent(getIntent());
-        mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
-        mViewSwitcher.setFactory(this);
-        mViewSwitcher.getCurrentView().requestFocus();
-        mProgressBar = (ProgressBar) findViewById(R.id.progress_circular);
-    }
-
-    public View makeView() {
-        WeekView wv = new WeekView(this);
-        wv.setId(VIEW_ID);
-        wv.setLayoutParams(new ViewSwitcher.LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        wv.setSelectedDay(mSelectedDay);
-        return wv;
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        long timeMillis = Utils.timeFromIntentInMillis(intent);
-        if (timeMillis > 0) {
-            Time time = new Time(Utils.getTimeZone(this, null));
-            time.set(timeMillis);
-            goTo(time, false);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        CalendarView view1 = (CalendarView) mViewSwitcher.getCurrentView();
-        CalendarView view2 = (CalendarView) mViewSwitcher.getNextView();
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
-
-        String str = prefs.getString(CalendarPreferenceActivity.KEY_DETAILED_VIEW,
-                CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW);
-        view1.setDetailedView(str);
-        view2.setDetailedView(str);
-
-        // Record Week View as the (new) start view
-        Utils.setDefaultView(this, CalendarApplication.WEEK_VIEW_ID);
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
-        mSelectedDay = view.getSelectedDay();
-    }
-}
diff --git a/src/com/android/calendar/WeekView.java b/src/com/android/calendar/WeekView.java
deleted file mode 100644
index ebff44c..0000000
--- a/src/com/android/calendar/WeekView.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2006 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.calendar;
-
-
-public class WeekView extends CalendarView {
-    private static final int CELL_MARGIN = 0;
-
-    public WeekView(CalendarActivity activity) {
-        super(activity);
-        init();
-    }
-
-    private void init() {
-        mDrawTextInEventRect = true;
-        mNumDays = 7;
-        mEventGeometry.setCellMargin(CELL_MARGIN);
-    }
-}
diff --git a/src/com/android/calendar/AgendaAdapter.java b/src/com/android/calendar/agenda/AgendaAdapter.java
similarity index 83%
rename from src/com/android/calendar/AgendaAdapter.java
rename to src/com/android/calendar/agenda/AgendaAdapter.java
index 8dcd8e9..0e74102 100644
--- a/src/com/android/calendar/AgendaAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaAdapter.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.agenda;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -22,14 +25,28 @@
 import android.provider.Calendar.Attendees;
 import android.text.format.DateFormat;
 import android.text.format.DateUtils;
+import android.text.format.Time;
 import android.view.View;
 import android.widget.ResourceCursorAdapter;
 import android.widget.TextView;
 
+import java.util.Formatter;
+import java.util.Locale;
+
 public class AgendaAdapter extends ResourceCursorAdapter {
     private String mNoTitleLabel;
     private Resources mResources;
     private int mDeclinedColor;
+    // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread.
+    private Formatter mFormatter;
+    private StringBuilder mStringBuilder;
+
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            notifyDataSetChanged();
+        }
+    };
 
     static class ViewHolder {
         int overLayColor; // Used by AgendaItemView to gray out the entire item if so desired
@@ -43,9 +60,12 @@
 
     public AgendaAdapter(Context context, int resource) {
         super(context, resource, null);
+
         mResources = context.getResources();
         mNoTitleLabel = mResources.getString(R.string.no_title_label);
         mDeclinedColor = mResources.getColor(R.drawable.agenda_item_declined);
+        mStringBuilder = new StringBuilder(50);
+        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
     }
 
     @Override
@@ -95,18 +115,22 @@
         long begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
         long end = cursor.getLong(AgendaWindowAdapter.INDEX_END);
         boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
-        int flags;
+        int flags = 0;
         String whenString;
+        // It's difficult to update all the adapters so just query this each
+        // time we need to build the view.
+        String tz = Utils.getTimeZone(context, mTZUpdater);;
         if (allDay) {
-            flags = DateUtils.FORMAT_UTC;
+            tz = Time.TIMEZONE_UTC;
         } else {
             flags = DateUtils.FORMAT_SHOW_TIME;
         }
         if (DateFormat.is24HourFormat(context)) {
             flags |= DateUtils.FORMAT_24HOUR;
         }
-
-        whenString = Utils.formatDateRange(context, begin, end, flags).toString();
+        mStringBuilder.setLength(0);
+        whenString = DateUtils.formatDateRange(context, mFormatter, begin, end, flags, tz)
+                .toString();
         when.setText(whenString);
 
         String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE);
@@ -121,7 +145,7 @@
         /*
         // Repeating info
         View repeatContainer = view.findViewById(R.id.repeat_icon);
-        String rrule = cursor.getString(AgendaActivity.INDEX_RRULE);
+        String rrule = cursor.getString(AgendaWindowAdapter.INDEX_RRULE);
         if (rrule != null) {
             repeatContainer.setVisibility(View.VISIBLE);
         } else {
@@ -131,9 +155,9 @@
 
         /*
         // Reminder
-        boolean hasAlarm = cursor.getInt(AgendaActivity.INDEX_HAS_ALARM) != 0;
+        boolean hasAlarm = cursor.getInt(AgendaWindowAdapter.INDEX_HAS_ALARM) != 0;
         if (hasAlarm) {
-            updateReminder(view, context, begin, cursor.getLong(AgendaActivity.INDEX_EVENT_ID));
+            updateReminder(view, context, begin, cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID));
         }
         */
 
diff --git a/src/com/android/calendar/AgendaByDayAdapter.java b/src/com/android/calendar/agenda/AgendaByDayAdapter.java
similarity index 88%
rename from src/com/android/calendar/AgendaByDayAdapter.java
rename to src/com/android/calendar/agenda/AgendaByDayAdapter.java
index acaabc7..55242d8 100644
--- a/src/com/android/calendar/AgendaByDayAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaByDayAdapter.java
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.agenda;
 
-import com.android.calendar.AgendaWindowAdapter.DayAdapterInfo;
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo;
 
 import android.content.Context;
 import android.database.Cursor;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.text.format.Time;
 import android.view.LayoutInflater;
@@ -29,8 +32,10 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Formatter;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.Locale;
 
 public class AgendaByDayAdapter extends BaseAdapter {
     private static final int TYPE_DAY = 0;
@@ -42,18 +47,33 @@
     private final LayoutInflater mInflater;
     private ArrayList<RowInfo> mRowInfo;
     private int mTodayJulianDay;
-
-    // Placeholder if we need some code for updating the tz later.
-    private Runnable mUpdateTZ = null;
+    private Time mTmpTime;
+    private String mTimeZone;
+    // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread.
+    private Formatter mFormatter;
+    private StringBuilder mStringBuilder;
 
     static class ViewHolder {
         TextView dateView;
     }
 
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            mTimeZone = Utils.getTimeZone(mContext, this);
+            mTmpTime = new Time(mTimeZone);
+            notifyDataSetChanged();
+        }
+    };
+
     public AgendaByDayAdapter(Context context) {
         mContext = context;
         mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item);
         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mStringBuilder = new StringBuilder(50);
+        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+        mTimeZone = Utils.getTimeZone(context, mTZUpdater);
+        mTmpTime = new Time(mTimeZone);
     }
 
     public int getCount() {
@@ -127,19 +147,29 @@
                 agendaDayView.setTag(holder);
             }
 
-            Time date = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+            // Re-use the member variable "mTime" which is set to the local
+            // time zone.
+            // It's difficult to find and update all these adapters when the
+            // home tz changes so check it here and update if needed.
+            String tz = Utils.getTimeZone(mContext, mTZUpdater);
+            if (!TextUtils.equals(tz, mTmpTime.timezone)) {
+                mTimeZone = tz;
+                mTmpTime = new Time(tz);
+            }
+            Time date = mTmpTime;
             long millis = date.setJulianDay(row.mData);
             int flags = DateUtils.FORMAT_SHOW_YEAR
                     | DateUtils.FORMAT_SHOW_DATE;
 
+            mStringBuilder.setLength(0);
             String dateViewText;
             if (row.mData == mTodayJulianDay) {
-                dateViewText = mContext.getString(R.string.agenda_today, Utils.formatDateRange(
-                        mContext, millis, millis, flags).toString());
+                dateViewText = mContext.getString(R.string.agenda_today, DateUtils.formatDateRange(
+                        mContext, mFormatter, millis, millis, flags, mTimeZone).toString());
             } else {
                 flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
-                dateViewText = Utils.formatDateRange(mContext, millis, millis,
-                        flags).toString();
+                dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis,
+                        flags, mTimeZone).toString();
             }
 
             if (AgendaWindowAdapter.BASICLOG) {
@@ -176,7 +206,7 @@
         Cursor cursor = dayAdapterInfo.cursor;
         ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
         int prevStartDay = -1;
-        Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+        Time time = new Time(mTimeZone);
         long now = System.currentTimeMillis();
         time.set(now);
         mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
@@ -270,7 +300,7 @@
         mRowInfo = rowInfo;
     }
 
-    static class RowInfo {
+    private static class RowInfo {
         // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
         final int mType;
 
diff --git a/src/com/android/calendar/agenda/AgendaFragment.java b/src/com/android/calendar/agenda/AgendaFragment.java
new file mode 100644
index 0000000..297ed44
--- /dev/null
+++ b/src/com/android/calendar/agenda/AgendaFragment.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2007 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.calendar.agenda;
+
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.GeneralPreferences;
+import com.android.calendar.Utils;
+
+import dalvik.system.VMRuntime;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class AgendaFragment extends Fragment implements CalendarController.EventHandler {
+
+    private static final String TAG = AgendaFragment.class.getSimpleName();
+    private static boolean DEBUG = false;
+
+    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
+
+    private AgendaListView mAgendaListView;
+    private Time mTime;
+    private String mTimeZone;
+
+    private String mQuery;
+
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            mTimeZone = Utils.getTimeZone(getActivity(), this);
+            mTime = new Time(mTimeZone);
+        }
+    };
+
+    public AgendaFragment() {
+        this(0);
+    }
+
+    public AgendaFragment(long timeMillis) {
+
+        mTimeZone = Utils.getTimeZone(getActivity(), mTZUpdater);
+        mTime = new Time(mTimeZone);
+        if (timeMillis == 0) {
+            mTime.setToNow();
+        } else {
+            mTime.set(timeMillis);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        // Eliminate extra GCs during startup by setting the initial heap size to 4MB.
+        // TODO: We should restore the old heap size once the activity reaches the idle state
+        VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        Context context = getActivity();
+        mAgendaListView = new AgendaListView(context);
+        mAgendaListView.goTo(mTime, mQuery, false);
+        return mAgendaListView;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (DEBUG) {
+            Log.v(TAG, "OnResume to " + mTime.toString());
+        }
+
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(
+                getActivity());
+        boolean hideDeclined = prefs.getBoolean(
+                GeneralPreferences.KEY_HIDE_DECLINED, false);
+
+        mAgendaListView.setHideDeclinedEvents(hideDeclined);
+        mAgendaListView.goTo(mTime, mQuery, true);
+        mAgendaListView.onResume();
+
+//        // Register for Intent broadcasts
+//        IntentFilter filter = new IntentFilter();
+//        filter.addAction(Intent.ACTION_TIME_CHANGED);
+//        filter.addAction(Intent.ACTION_DATE_CHANGED);
+//        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+//        registerReceiver(mIntentReceiver, filter);
+//
+//        mContentResolver.registerContentObserver(Events.CONTENT_URI, true, mObserver);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        long firstVisibleTime = mAgendaListView.getFirstVisibleTime();
+        if (firstVisibleTime > 0) {
+            mTime.set(firstVisibleTime);
+            outState.putLong(BUNDLE_KEY_RESTORE_TIME, firstVisibleTime);
+            if (DEBUG) {
+                Log.v(TAG, "onSaveInstanceState " + mTime.toString());
+            }
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        mAgendaListView.onPause();
+//        mContentResolver.unregisterContentObserver(mObserver);
+//        unregisterReceiver(mIntentReceiver);
+
+        // Record Agenda View as the (new) default detailed view.
+//        Utils.setDefaultView(this, CalendarApplication.AGENDA_VIEW_ID);
+    }
+
+    /* Navigator interface methods */
+    @Override
+    public void goToToday() {
+        if (mAgendaListView == null) {
+         // The view hasn't been set yet. Just save the time and use it later.
+            mTime.setToNow();
+            return;
+        }
+        Time now = new Time(mTimeZone);
+        now.setToNow();
+        mAgendaListView.goTo(now, mQuery, true); // Force refresh
+    }
+
+    @Override
+    public void goTo(Time time, boolean animate) {
+        if (mAgendaListView == null) {
+            // The view hasn't been set yet. Just save the time and use it
+            // later.
+            mTime.set(time);
+            return;
+        }
+        mAgendaListView.goTo(time, mQuery, false);
+    }
+
+    private void search(String query, Time time) {
+        mQuery = query;
+        if (time != null) {
+            mTime.set(time);
+        }
+        if (mAgendaListView == null) {
+            // The view hasn't been set yet. Just return.
+            return;
+        }
+        mAgendaListView.goTo(time, mQuery, true);
+    }
+
+    @Override
+    public long getSelectedTime() {
+        return mAgendaListView.getSelectedTime();
+    }
+
+    @Override
+    public boolean getAllDay() {
+        return false;
+    }
+
+    @Override
+    public void eventsChanged() {
+        mAgendaListView.refresh(true);
+    }
+
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.GO_TO | EventType.EVENTS_CHANGED | EventType.SEARCH;
+    }
+
+    @Override
+    public void handleEvent(EventInfo event) {
+        if (event.eventType == EventType.GO_TO) {
+            // TODO support a range of time
+            // TODO support event_id
+            // TODO figure out the animate bit
+            goTo(event.startTime, true);
+        } else if (event.eventType == EventType.SEARCH) {
+            search(event.query, event.startTime);
+        } else if (event.eventType == EventType.EVENTS_CHANGED) {
+            eventsChanged();
+        }
+    }
+}
+
diff --git a/src/com/android/calendar/AgendaItemView.java b/src/com/android/calendar/agenda/AgendaItemView.java
similarity index 69%
rename from src/com/android/calendar/AgendaItemView.java
rename to src/com/android/calendar/agenda/AgendaItemView.java
index 8a7f65d..9f314ea 100644
--- a/src/com/android/calendar/AgendaItemView.java
+++ b/src/com/android/calendar/agenda/AgendaItemView.java
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.agenda;
 
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.os.Bundle;
 import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.RelativeLayout;
 
-import com.android.calendar.AgendaAdapter.ViewHolder;
+import com.android.calendar.agenda.AgendaAdapter.ViewHolder;
 
 /**
  * A custom layout for each item in the Agenda list view.
@@ -32,8 +30,6 @@
 public class AgendaItemView extends RelativeLayout {
     Paint mPaint = new Paint();
 
-    private Bundle mTempEventBundle;
-
     public AgendaItemView(Context context) {
         super(context);
     }
@@ -58,19 +54,4 @@
             }
         }
     }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (mTempEventBundle == null) {
-            mTempEventBundle = new Bundle();
-        }
-        ViewHolder holder = (ViewHolder) getTag();
-        if (holder != null) {
-            // this way to be consistent with CalendarView
-            Bundle bundle = mTempEventBundle;
-            bundle.putInt("color", holder.calendarColor);
-            event.setParcelableData(bundle);
-        }
-        return super.dispatchPopulateAccessibilityEvent(event);
-    }
 }
diff --git a/src/com/android/calendar/AgendaListView.java b/src/com/android/calendar/agenda/AgendaListView.java
similarity index 72%
rename from src/com/android/calendar/AgendaListView.java
rename to src/com/android/calendar/agenda/AgendaListView.java
index 3b9593c..263a3a5 100644
--- a/src/com/android/calendar/AgendaListView.java
+++ b/src/com/android/calendar/agenda/AgendaListView.java
@@ -14,21 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.agenda;
 
-import com.android.calendar.AgendaAdapter.ViewHolder;
-import com.android.calendar.AgendaWindowAdapter.EventInfo;
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.DeleteEventHelper;
+import com.android.calendar.Utils;
+import com.android.calendar.agenda.AgendaAdapter.ViewHolder;
+import com.android.calendar.agenda.AgendaWindowAdapter.EventInfo;
 
-import android.content.ContentUris;
-import android.content.Intent;
+import android.content.Context;
 import android.graphics.Rect;
-import android.net.Uri;
-import android.provider.Calendar;
-import android.provider.Calendar.Events;
 import android.text.format.Time;
 import android.util.Log;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
@@ -40,30 +39,28 @@
     private static final boolean DEBUG = false;
 
     private AgendaWindowAdapter mWindowAdapter;
-
-    private AgendaActivity mAgendaActivity;
     private DeleteEventHelper mDeleteEventHelper;
+    private Context mContext;
+    private String mTimeZone;
 
-    /**
-     * Reusable view to fetch the day title for accessibility support with no
-     * code duplication reducing fragility.
-     */
-    private View mLazyTempView;
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            mTimeZone = Utils.getTimeZone(mContext, this);
+        }
+    };
 
-    // Placeholder if we need some code for updating the tz later.
-    private Runnable mUpdateTZ = null;
-
-    public AgendaListView(AgendaActivity agendaActivity) {
-        super(agendaActivity, null);
-        mAgendaActivity = agendaActivity;
-
+    public AgendaListView(Context context) {
+        super(context, null);
+        mContext = context;
+        mTimeZone = Utils.getTimeZone(context, mTZUpdater);
         setOnItemClickListener(this);
         setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         setVerticalScrollBarEnabled(false);
-        mWindowAdapter = new AgendaWindowAdapter(agendaActivity, this);
+        mWindowAdapter = new AgendaWindowAdapter(context, this);
         setAdapter(mWindowAdapter);
         mDeleteEventHelper =
-            new DeleteEventHelper(agendaActivity, false /* don't exit when done */);
+            new DeleteEventHelper(context, null, false /* don't exit when done */);
     }
 
     @Override protected void onDetachedFromWindow() {
@@ -71,54 +68,38 @@
         mWindowAdapter.close();
     }
 
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        int titlePosition = 0 ;
-        AgendaByDayAdapter.RowInfo rowInfo = null;
-        // find the index of the item with day title
-        for (titlePosition = getSelectedItemPosition() - 1; titlePosition >= 0; titlePosition--) {
-            Object previousItem = mWindowAdapter.getItem(titlePosition);
-            if (previousItem instanceof AgendaByDayAdapter.RowInfo) {
-                break;
-            }
-        }
-        // append the day title
-        mLazyTempView = mWindowAdapter.getView(titlePosition, mLazyTempView, null);
-        if (mLazyTempView != null && mLazyTempView instanceof TextView) {
-            TextView weekDayTitleView = (TextView) mLazyTempView;
-            CharSequence weekDayTitleViewText = weekDayTitleView.getText();
-            event.getText().add(weekDayTitleViewText);
-        }
-        return super.dispatchPopulateAccessibilityEvent(event);
-    }
-
     // Implementation of the interface OnItemClickListener
     public void onItemClick(AdapterView<?> a, View v, int position, long id) {
         if (id != -1) {
             // Switch to the EventInfo view
             EventInfo event = mWindowAdapter.getEventByPosition(position);
             if (event != null) {
-                Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, event.id);
-                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-                intent.putExtra(Calendar.EVENT_BEGIN_TIME, event.begin);
-                intent.putExtra(Calendar.EVENT_END_TIME, event.end);
-                mAgendaActivity.startActivity(intent);
+                CalendarController.getInstance(mContext).sendEventRelatedEvent(this,
+                        EventType.VIEW_EVENT, event.id, event.begin, event.end, 0, 0);
             }
         }
     }
 
-    public void goTo(Time time, boolean forced) {
-        mWindowAdapter.refresh(time, forced);
+    public void goTo(Time time, String searchQuery, boolean forced) {
+        if (time == null) {
+            time = new Time(mTimeZone);
+            long goToTime = getFirstVisibleTime();
+            if (goToTime <= 0) {
+                goToTime = System.currentTimeMillis();
+            }
+            time.set(goToTime);
+        }
+        mWindowAdapter.refresh(time, searchQuery, forced);
     }
 
     public void refresh(boolean forced) {
-        Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+        Time time = new Time(Utils.getTimeZone(mContext, null));
         long goToTime = getFirstVisibleTime();
         if (goToTime <= 0) {
             goToTime = System.currentTimeMillis();
         }
         time.set(goToTime);
-        mWindowAdapter.refresh(time, forced);
+        mWindowAdapter.refresh(time, null, forced);
     }
 
     public void deleteSelectedEvent() {
@@ -235,7 +216,8 @@
     }
 
     public void onResume() {
-        mWindowAdapter.notifyDataSetChanged();
+        mTZUpdater.run();
+        mWindowAdapter.onResume();
     }
     public void onPause() {
         mWindowAdapter.notifyDataSetInvalidated();
diff --git a/src/com/android/calendar/AgendaWindowAdapter.java b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
similarity index 88%
rename from src/com/android/calendar/AgendaWindowAdapter.java
rename to src/com/android/calendar/agenda/AgendaWindowAdapter.java
index ff74a12..ee5335b 100644
--- a/src/com/android/calendar/AgendaWindowAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.agenda;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
 
 import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.provider.Calendar;
 import android.provider.Calendar.Attendees;
 import android.provider.Calendar.Calendars;
 import android.provider.Calendar.Instances;
@@ -34,8 +39,10 @@
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
+import java.util.Formatter;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.Locale;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 /*
@@ -62,7 +69,11 @@
     static final boolean DEBUGLOG = false;
     private static String TAG = "AgendaWindowAdapter";
 
-    private static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC";
+    private static final String AGENDA_SORT_ORDER =
+        Calendar.Instances.START_DAY + " ASC, " +
+        Calendar.Instances.BEGIN + " ASC, " +
+        Calendar.Events.TITLE + " ASC";
+
     public static final int INDEX_TITLE = 1;
     public static final int INDEX_EVENT_LOCATION = 2;
     public static final int INDEX_ALL_DAY = 3;
@@ -106,8 +117,8 @@
 
     private static final int PREFETCH_BOUNDARY = 1;
 
-    // Times to auto-expand/retry query after getting no data
-    private static final int RETRIES_ON_NO_DATA = 0;
+    /** Times to auto-expand/retry query after getting no data */
+    private static final int RETRIES_ON_NO_DATA = 1;
 
     private Context mContext;
 
@@ -115,11 +126,14 @@
 
     private AgendaListView mAgendaListView;
 
-    private int mRowCount; // The sum of the rows in all the adapters
+    /** The sum of the rows in all the adapters */
+    private int mRowCount;
 
+    /** The number of times we have queried and gotten no results back */
     private int mEmptyCursorCount;
 
-    private DayAdapterInfo mLastUsedInfo; // Cached value of the last used adapter.
+    /** Cached value of the last used adapter */
+    private DayAdapterInfo mLastUsedInfo;
 
     private LinkedList<DayAdapterInfo> mAdapterInfos = new LinkedList<DayAdapterInfo>();
 
@@ -131,37 +145,50 @@
 
     private boolean mDoneSettingUpHeaderFooter = false;
 
-    /*
+    /**
      * When the user scrolled to the top, a query will be made for older events
      * and this will be incremented. Don't make more requests if
      * mOlderRequests > mOlderRequestsProcessed.
      */
     private int mOlderRequests;
 
-    // Number of "older" query that has been processed.
+    /** Number of "older" query that has been processed. */
     private int mOlderRequestsProcessed;
 
-    /*
+    /**
      * When the user scrolled to the bottom, a query will be made for newer
      * events and this will be incremented. Don't make more requests if
      * mNewerRequests > mNewerRequestsProcessed.
      */
     private int mNewerRequests;
 
-    // Number of "newer" query that has been processed.
+    /** Number of "newer" query that has been processed. */
     private int mNewerRequestsProcessed;
 
+    // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread.
+    private Formatter mFormatter;
+    private StringBuilder mStringBuilder;
+    private String mTimeZone;
+
+    private Runnable mTZUpdater = new Runnable() {
+        @Override
+        public void run() {
+            mTimeZone = Utils.getTimeZone(mContext, this);
+            notifyDataSetChanged();
+        }
+    };
+
     private boolean mShuttingDown;
     private boolean mHideDeclined;
 
+    /** The current search query, or null if none */
+    private String mSearchQuery;
+
     // Types of Query
     private static final int QUERY_TYPE_OLDER = 0; // Query for older events
     private static final int QUERY_TYPE_NEWER = 1; // Query for newer events
     private static final int QUERY_TYPE_CLEAN = 2; // Delete everything and query around a date
 
-    // Placeholder if we need some code for updating the tz later.
-    private Runnable mUpdateTZ = null;
-
     private static class QuerySpec {
         long queryStartMillis;
 
@@ -171,6 +198,8 @@
 
         int end;
 
+        String searchQuery;
+
         int queryType;
 
         public QuerySpec(int queryType) {
@@ -185,6 +214,7 @@
             result = prime * result + (int) (queryStartMillis ^ (queryStartMillis >>> 32));
             result = prime * result + queryType;
             result = prime * result + start;
+            result = prime * result + searchQuery.hashCode();
             if (goToTime != null) {
                 long goToTimeMillis = goToTime.toMillis(false);
                 result = prime * result + (int) (goToTimeMillis ^ (goToTimeMillis >>> 32));
@@ -199,9 +229,11 @@
             if (getClass() != obj.getClass()) return false;
             QuerySpec other = (QuerySpec) obj;
             if (end != other.end || queryStartMillis != other.queryStartMillis
-                    || queryType != other.queryType || start != other.start) {
+                    || queryType != other.queryType || start != other.start
+                    || Utils.equals(searchQuery, other.searchQuery)) {
                 return false;
             }
+
             if (goToTime != null) {
                 if (goToTime.toMillis(false) != other.goToTime.toMillis(false)) {
                     return false;
@@ -242,6 +274,8 @@
 
         @Override
         public String toString() {
+            // Static class, so the time in this toString will not reflect the
+            // home tz settings. This should only affect debugging.
             Time time = new Time();
             StringBuilder sb = new StringBuilder();
             time.setJulianDay(start);
@@ -256,13 +290,19 @@
         }
     }
 
-    public AgendaWindowAdapter(AgendaActivity agendaActivity,
+    public AgendaWindowAdapter(Context context,
             AgendaListView agendaListView) {
-        mContext = agendaActivity;
+        mContext = context;
+        mTimeZone = Utils.getTimeZone(context, mTZUpdater);
         mAgendaListView = agendaListView;
-        mQueryHandler = new QueryHandler(agendaActivity.getContentResolver());
+        mQueryHandler = new QueryHandler(context.getContentResolver());
 
-        LayoutInflater inflater = (LayoutInflater) agendaActivity
+        mStringBuilder = new StringBuilder(50);
+        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+        mSearchQuery = null;
+
+        LayoutInflater inflater = (LayoutInflater) context
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mHeaderView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null);
         mFooterView = (TextView)inflater.inflate(R.layout.agenda_header_footer, null);
@@ -441,11 +481,11 @@
             boolean allDay = info.cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
 
             if (allDay) { // UTC
-                Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+                Time time = new Time(mTimeZone);
                 time.setJulianDay(Time.getJulianDay(event.begin, 0));
                 event.begin = time.toMillis(false /* use isDst */);
             } else if (isDayHeader) { // Trim to midnight.
-                Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+                Time time = new Time(mTimeZone);
                 time.set(event.begin);
                 time.hour = 0;
                 time.minute = 0;
@@ -462,7 +502,11 @@
         return null;
     }
 
-    public void refresh(Time goToTime, boolean forced) {
+    public void refresh(Time goToTime, String searchQuery, boolean forced) {
+        if (searchQuery != null) {
+            mSearchQuery = searchQuery;
+        }
+
         if (DEBUGLOG) {
             Log.e(TAG, "refresh " + goToTime.toString() + (forced ? " forced" : " not forced"));
         }
@@ -478,7 +522,7 @@
         // Query for a total of MIN_QUERY_DURATION days
         int endDay = startDay + MIN_QUERY_DURATION;
 
-        queueQuery(startDay, endDay, goToTime, QUERY_TYPE_CLEAN);
+        queueQuery(startDay, endDay, goToTime, searchQuery, QUERY_TYPE_CLEAN);
     }
 
     public void close() {
@@ -516,6 +560,7 @@
                     do {
                         info = mAdapterInfos.poll();
                         if (info != null) {
+                            // TODO the following causes ANR's. Do this in a thread.
                             info.cursor.close();
                             deletedRows += info.size;
                             recycleMe = info;
@@ -544,13 +589,17 @@
         }
     }
 
-    private Uri buildQueryUri(int start, int end) {
-        StringBuilder path = new StringBuilder();
-        path.append(start);
-        path.append('/');
-        path.append(end);
-        Uri uri = Uri.withAppendedPath(Instances.CONTENT_BY_DAY_URI, path.toString());
-        return uri;
+    private Uri buildQueryUri(int start, int end, String searchQuery) {
+        Uri rootUri = searchQuery == null ?
+                Instances.CONTENT_BY_DAY_URI :
+                Instances.CONTENT_SEARCH_BY_DAY_URI;
+        Uri.Builder builder = rootUri.buildUpon();
+        ContentUris.appendId(builder, start);
+        ContentUris.appendId(builder, end);
+        if (searchQuery != null) {
+            builder.appendPath(searchQuery);
+        }
+        return builder.build();
     }
 
     private boolean isInRange(int start, int end) {
@@ -577,15 +626,18 @@
         return queryDuration;
     }
 
-    private boolean queueQuery(int start, int end, Time goToTime, int queryType) {
+    private boolean queueQuery(int start, int end, Time goToTime,
+            String searchQuery, int queryType) {
         QuerySpec queryData = new QuerySpec(queryType);
         queryData.goToTime = goToTime;
         queryData.start = start;
         queryData.end = end;
+        queryData.searchQuery = searchQuery;
         return queueQuery(queryData);
     }
 
     private boolean queueQuery(QuerySpec queryData) {
+        queryData.searchQuery = mSearchQuery;
         Boolean queuedQuery;
         synchronized (mQueryQueue) {
             queuedQuery = false;
@@ -617,9 +669,9 @@
         }
 
         if (BASICLOG) {
-            Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+            Time time = new Time(mTimeZone);
             time.setJulianDay(queryData.start);
-            Time time2 = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+            Time time2 = new Time(mTimeZone);
             time2.setJulianDay(queryData.end);
             Log.v(TAG, "startQuery: " + time.toString() + " to "
                     + time2.toString() + " then go to " + queryData.goToTime);
@@ -627,18 +679,22 @@
 
         mQueryHandler.cancelOperation(0);
         if (BASICLOG) queryData.queryStartMillis = System.nanoTime();
-        mQueryHandler.startQuery(0, queryData, buildQueryUri(
-                queryData.start, queryData.end), PROJECTION,
-                buildQuerySelection(), null, AGENDA_SORT_ORDER);
+
+        Uri queryUri = buildQueryUri(
+                queryData.start, queryData.end, queryData.searchQuery);
+        mQueryHandler.startQuery(0, queryData, queryUri,
+                PROJECTION, buildQuerySelection(), null,
+                AGENDA_SORT_ORDER);
     }
 
     private String formatDateString(int julianDay) {
-        Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+        Time time = new Time(mTimeZone);
         time.setJulianDay(julianDay);
         long millis = time.toMillis(false);
-        return Utils.formatDateRange(mContext, millis, millis,
+        mStringBuilder.setLength(0);
+        return DateUtils.formatDateRange(mContext, mFormatter, millis, millis,
                 DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE
-                        | DateUtils.FORMAT_ABBREV_MONTH).toString();
+                        | DateUtils.FORMAT_ABBREV_MONTH, mTimeZone).toString();
     }
 
     private void updateHeaderFooter(final int start, final int end) {
@@ -874,6 +930,10 @@
         return title;
     }
 
+    public void onResume() {
+        mTZUpdater.run();
+    }
+
     public void setHideDeclinedEvents(boolean hideDeclined) {
         mHideDeclined = hideDeclined;
     }
diff --git a/src/com/android/calendar/AlertActivity.java b/src/com/android/calendar/alerts/AlertActivity.java
similarity index 91%
rename from src/com/android/calendar/AlertActivity.java
rename to src/com/android/calendar/alerts/AlertActivity.java
index 1e35c50..b6a55f2 100644
--- a/src/com/android/calendar/AlertActivity.java
+++ b/src/com/android/calendar/alerts/AlertActivity.java
@@ -14,27 +14,28 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.alerts;
 
-import static android.provider.Calendar.EVENT_BEGIN_TIME;
-import static android.provider.Calendar.EVENT_END_TIME;
+import com.android.calendar.AsyncQueryService;
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.R;
+import com.android.calendar.R.id;
+import com.android.calendar.R.layout;
+import com.android.calendar.R.string;
+import com.android.calendar.Utils;
 
 import android.app.Activity;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
-import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Calendar.CalendarAlerts;
 import android.provider.Calendar.CalendarAlertsColumns;
-import android.provider.Calendar.Events;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -105,7 +106,7 @@
         values.put(PROJECTION[INDEX_STATE], CalendarAlerts.DISMISSED);
         String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
         mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
-                selection, null /* selectionArgs */);
+                selection, null /* selectionArgs */, Utils.UNDO_DELAY);
     }
 
     private void dismissAlarm(long id) {
@@ -113,12 +114,12 @@
         values.put(PROJECTION[INDEX_STATE], CalendarAlerts.DISMISSED);
         String selection = CalendarAlerts._ID + "=" + id;
         mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
-                selection, null /* selectionArgs */);
+                selection, null /* selectionArgs */, Utils.UNDO_DELAY);
     }
 
-    private class QueryHandler extends AsyncQueryHandler {
-        public QueryHandler(ContentResolver cr) {
-            super(cr);
+    private class QueryHandler extends AsyncQueryService {
+        public QueryHandler(Context context) {
+            super(context);
         }
 
         @Override
@@ -179,20 +180,15 @@
             AlertActivity alertActivity = AlertActivity.this;
             Cursor cursor = alertActivity.getItemForView(view);
 
-            long id = cursor.getInt(AlertActivity.INDEX_EVENT_ID);
-            long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
-            long endMillis = cursor.getLong(AlertActivity.INDEX_END);
-
-            Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, id);
-            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-            intent.setClass(alertActivity, EventInfoActivity.class);
-            intent.putExtra(EVENT_BEGIN_TIME, startMillis);
-            intent.putExtra(EVENT_END_TIME, endMillis);
-
             // Mark this alarm as DISMISSED
             dismissAlarm(cursor.getLong(INDEX_ROW_ID));
 
-            startActivity(intent);
+            long id = cursor.getInt(AlertActivity.INDEX_EVENT_ID);
+            long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
+            long endMillis = cursor.getLong(AlertActivity.INDEX_END);
+            CalendarController.getInstance(alertActivity).sendEventRelatedEvent(alertActivity,
+                    EventType.VIEW_EVENT, id, startMillis, endMillis, 0, 0);
+
             alertActivity.finish();
         }
     };
@@ -211,7 +207,7 @@
         getWindow().setAttributes(lp);
 
         mResolver = getContentResolver();
-        mQueryHandler = new QueryHandler(mResolver);
+        mQueryHandler = new QueryHandler(this);
         mAdapter = new AlertAdapter(this, R.layout.alert_item);
 
         mListView = (ListView) findViewById(R.id.alert_container);
@@ -286,7 +282,8 @@
                 if (mCursor.isLast()) {
                     scheduleAlarmTime = alarmTime;
                 }
-                mQueryHandler.startInsert(0, scheduleAlarmTime, CalendarAlerts.CONTENT_URI, values);
+                mQueryHandler.startInsert(0, scheduleAlarmTime, CalendarAlerts.CONTENT_URI, values,
+                        Utils.UNDO_DELAY);
             }
 
             dismissFiredAlarms();
diff --git a/src/com/android/calendar/AlertAdapter.java b/src/com/android/calendar/alerts/AlertAdapter.java
similarity index 94%
rename from src/com/android/calendar/AlertAdapter.java
rename to src/com/android/calendar/alerts/AlertAdapter.java
index 03a2151..ebd265b 100644
--- a/src/com/android/calendar/AlertAdapter.java
+++ b/src/com/android/calendar/alerts/AlertAdapter.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.alerts;
+
+import com.android.calendar.R;
+import com.android.calendar.R.id;
+import com.android.calendar.R.string;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -93,7 +97,7 @@
         if (DateFormat.is24HourFormat(context)) {
             flags |= DateUtils.FORMAT_24HOUR;
         }
-        when = Utils.formatDateRange(context, startMillis, endMillis, flags);
+        when = DateUtils.formatDateRange(context, startMillis, endMillis, flags);
         textView = (TextView) view.findViewById(R.id.when);
         textView.setText(when);
         
diff --git a/src/com/android/calendar/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java
similarity index 94%
rename from src/com/android/calendar/AlertReceiver.java
rename to src/com/android/calendar/alerts/AlertReceiver.java
index 8ea7035..456b3d8 100644
--- a/src/com/android/calendar/AlertReceiver.java
+++ b/src/com/android/calendar/alerts/AlertReceiver.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.alerts;
+
+import com.android.calendar.R;
+import com.android.calendar.R.drawable;
+import com.android.calendar.R.string;
 
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -113,8 +117,9 @@
     }
 
     /**
-     * Creates an alert notification. If high priority, this will attach a pending intent.
-     * Otherwise, it creates a standard notification.
+     * Creates an alert notification. If high priority, this will set
+     * FLAG_HIGH_PRIORITY on the resulting notification and attach the a pending
+     * intent. Otherwise, it creates a standard notification.
      */
     public static Notification makeNewAlertNotification(Context context,
             String title, String location, int numReminders,
@@ -162,6 +167,7 @@
         notification.deleteIntent = PendingIntent.getBroadcast(context, 0,
                 deleteIntent, 0);
         if (highPriority) {
+            notification.flags |= Notification.FLAG_HIGH_PRIORITY;
             notification.fullScreenIntent = pendingClickIntent;
         }
 
diff --git a/src/com/android/calendar/AlertService.java b/src/com/android/calendar/alerts/AlertService.java
similarity index 94%
rename from src/com/android/calendar/AlertService.java
rename to src/com/android/calendar/alerts/AlertService.java
index 8e620e4..54a15f9 100644
--- a/src/com/android/calendar/AlertService.java
+++ b/src/com/android/calendar/alerts/AlertService.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.calendar;
+package com.android.calendar.alerts;
+
+import com.android.calendar.GeneralPreferences;
+import com.android.calendar.R;
+import com.android.calendar.R.string;
 
 import android.app.AlarmManager;
 import android.app.Notification;
@@ -249,12 +253,13 @@
             }
         }
 
-        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(context);
-        String reminderType = prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_TYPE,
-                CalendarPreferenceActivity.ALERT_TYPE_STATUS_BAR);
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
+
+        boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
+        boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
 
         // TODO check for this before adding stuff to the alerts table.
-        if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_OFF)) {
+        if (!doAlert) {
             if (DEBUG) {
                 Log.d(TAG, "alert preference is OFF");
             }
@@ -262,8 +267,7 @@
         }
 
         boolean quietUpdate = numFired == 0;
-        boolean highPriority = numFired > 0 &&
-                reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_ALERTS);
+        boolean highPriority = numFired > 0 && doPopup;
         postNotification(context, prefs, notificationEventName, notificationEventLocation,
                 numReminders, quietUpdate, highPriority);
 
@@ -307,15 +311,15 @@
             // Find out the circumstances under which to vibrate.
             // Migrate from pre-Froyo boolean setting if necessary.
             String vibrateWhen; // "always" or "silent" or "never"
-            if(prefs.contains(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE_WHEN))
+            if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
             {
                 // Look up Froyo setting
                 vibrateWhen =
-                    prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE_WHEN, null);
-            } else if(prefs.contains(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE)) {
+                    prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
+            } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
                 // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
                 boolean vibrate =
-                    prefs.getBoolean(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE, false);
+                    prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
                 vibrateWhen = vibrate ?
                     context.getString(R.string.prefDefault_alerts_vibrate_true) :
                     context.getString(R.string.prefDefault_alerts_vibrate_false);
@@ -338,7 +342,7 @@
             // Possibly generate a sound. If 'Silent' is chosen, the ringtone
             // string will be empty.
             String reminderRingtone = prefs.getString(
-                    CalendarPreferenceActivity.KEY_ALERTS_RINGTONE, null);
+                    GeneralPreferences.KEY_ALERTS_RINGTONE, null);
             notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
                     .parse(reminderRingtone);
         }
diff --git a/src/com/android/calendar/DismissAllAlarmsService.java b/src/com/android/calendar/alerts/DismissAllAlarmsService.java
similarity index 97%
rename from src/com/android/calendar/DismissAllAlarmsService.java
rename to src/com/android/calendar/alerts/DismissAllAlarmsService.java
index fee76e0..c3d6b6e 100644
--- a/src/com/android/calendar/DismissAllAlarmsService.java
+++ b/src/com/android/calendar/alerts/DismissAllAlarmsService.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.calendar;
+package com.android.calendar.alerts;
 
 import android.app.IntentService;
 import android.content.ContentResolver;
diff --git a/src/com/android/calendar/event/AttendeesAdapter.java b/src/com/android/calendar/event/AttendeesAdapter.java
new file mode 100644
index 0000000..55ba9cb
--- /dev/null
+++ b/src/com/android/calendar/event/AttendeesAdapter.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.CalendarEventModel.Attendee;
+import com.android.calendar.ContactsAsyncHelper;
+import com.android.calendar.R;
+import com.android.calendar.event.EditEventHelper.AttendeeItem;
+import com.android.common.Rfc822Validator;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.Calendar.Attendees;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.QuickContact;
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+
+public class AttendeesAdapter extends BaseAdapter {
+    private static final String TAG = "AttendeesAdapter";
+    private static final boolean DEBUG = false;
+
+    int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0;
+    int PRESENCE_PROJECTION_PRESENCE_INDEX = 1;
+    int PRESENCE_PROJECTION_EMAIL_INDEX = 2;
+    int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3;
+
+    private static final String[] PRESENCE_PROJECTION = new String[] {
+        Email.CONTACT_ID,           // 0
+        Email.CONTACT_PRESENCE,     // 1
+        Email.DATA,                 // 2
+        Email.PHOTO_ID,             // 3
+    };
+
+    private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI;
+    private static final String CONTACT_DATA_SELECTION = Email.DATA + " IN (?)";
+
+    private Context mContext;
+    private Rfc822Validator mValidator;
+    private LayoutInflater mInflater;
+
+    private int mYes = 0;
+    private int mNo = 0;
+    private int mMaybe = 0;
+    private int mNoResponse = 0;
+
+    private Drawable mDefaultBadge;
+
+    private ArrayList<AttendeeItem> mAttendees;
+    private AttendeeItem[] mDividers = new AttendeeItem[4];
+    private RemoveAttendeeClickListener mRemoveListener = new RemoveAttendeeClickListener();
+    private PresenceQueryHandler mPresenceQueryHandler;
+
+    private class RemoveAttendeeClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View v) {
+            AttendeeItem item = (AttendeeItem) v.getTag();
+            item.mRemoved = !item.mRemoved;
+            notifyDataSetChanged();
+        }
+
+    }
+
+    public AttendeesAdapter(Context context, Rfc822Validator validator) {
+        Resources res = context.getResources();
+        mContext = context;
+        mValidator = validator;
+        mAttendees = new ArrayList<AttendeeItem>();
+        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mDefaultBadge = res.getDrawable(R.drawable.ic_contact_picture);
+
+        // Add the labels we need to our adapter
+        CharSequence[] entries;
+        entries = res.getTextArray(R.array.response_labels1);
+        // Yes
+        mDividers[0] = addDivider(context, entries[1]);
+        // No
+        mDividers[1] = addDivider(context, entries[3]);
+        // Maybe
+        mDividers[2] = addDivider(context, entries[2]);
+        // No Response
+        mDividers[3] = addDivider(context, entries[0]);
+
+        mPresenceQueryHandler = new PresenceQueryHandler(context.getContentResolver());
+    }
+
+    private AttendeeItem addDivider(Context context, CharSequence label) {
+        AttendeeItem labelItem = new AttendeeItem();
+        labelItem.mRemoved = true;
+        labelItem.mDivider = true;
+        labelItem.mDividerLabel = label.toString();
+        mAttendees.add(labelItem);
+        return labelItem;
+    }
+
+    public boolean contains(Attendee attendee) {
+        for (AttendeeItem item : mAttendees) {
+            if (item.mAttendee == null) {
+                continue;
+            }
+            if(TextUtils.equals(attendee.mEmail, item.mAttendee.mEmail)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addAttendee(Attendee attendee) {
+        int i = 0;
+        if (contains(attendee)) {
+            return;
+        }
+        int status = attendee.mStatus;
+        AttendeeItem startItem;
+        if (status == Attendees.ATTENDEE_STATUS_ACCEPTED) {
+            startItem = mDividers[0];
+            mYes++;
+        } else if (status == Attendees.ATTENDEE_STATUS_DECLINED) {
+            startItem = mDividers[1];
+            mNo++;
+        } else if (status == Attendees.ATTENDEE_STATUS_TENTATIVE){
+            startItem = mDividers[2];
+            mMaybe++;
+        } else {
+            startItem = mDividers[3];
+            mNoResponse++;
+        }
+        // Advance to the start of the section this name should go in
+        while (mAttendees.get(i++) != startItem);
+        int size = mAttendees.size();
+        String name = attendee.mName;
+        if (name == null) {
+            name = "";
+        }
+        while (true) {
+            if (i >= size) {
+                break;
+            }
+            AttendeeItem currItem = mAttendees.get(i);
+            if (currItem.mDivider) {
+                break;
+            }
+            if (name.compareToIgnoreCase(currItem.mAttendee.mName) < 0) {
+                break;
+            }
+            i++;
+        }
+        AttendeeItem item = new AttendeeItem();
+        item.mAttendee = attendee;
+        item.mPresence = -1;
+        item.mBadge = mDefaultBadge;
+        mAttendees.add(i, item);
+        // If we have any yes or no responses turn labels on where necessary
+        if (mYes > 0 || mNo > 0 || mMaybe > 0) {
+            startItem.mRemoved = false; // mView.setVisibility(View.VISIBLE);
+            if (DEBUG) {
+                Log.d(TAG, "Set " + startItem.mDividerLabel + " to visible");
+            }
+            if (mNoResponse > 0) {
+                mDividers[3].mRemoved = false; // mView.setVisibility(View.VISIBLE);
+                if (DEBUG) {
+                    Log.d(TAG, "Set " + mDividers[2].mDividerLabel + " to visible");
+                }
+            }
+        }
+        mPresenceQueryHandler.startQuery(item.mUpdateCounts + 1, item,
+                CONTACT_DATA_WITH_PRESENCE_URI, PRESENCE_PROJECTION, CONTACT_DATA_SELECTION,
+                new String[] { attendee.mEmail }, null);
+        notifyDataSetChanged();
+    }
+
+    public void addAttendees(ArrayList<Attendee> attendees) {
+        synchronized (mAttendees) {
+            for (Attendee attendee : attendees) {
+                addAttendee(attendee);
+            }
+        }
+    }
+
+    public void addAttendees(HashMap<String, Attendee> attendees) {
+        synchronized (mAttendees) {
+            for (Attendee attendee : attendees.values()) {
+                addAttendee(attendee);
+            }
+        }
+    }
+
+    public void addAttendees(String attendees) {
+        LinkedHashSet<Rfc822Token> addresses =
+            EditEventHelper.getAddressesFromList(attendees, mValidator);
+        synchronized (mAttendees) {
+            for (Rfc822Token address : addresses) {
+                Attendee attendee = new Attendee(address.getName(), address.getAddress());
+                if (TextUtils.isEmpty(attendee.mName)) {
+                    attendee.mName = attendee.mEmail;
+                }
+                addAttendee(attendee);
+            }
+        }
+    }
+
+    public void removeAttendee(int position) {
+        if (position < 0) {
+            return;
+        }
+        AttendeeItem item = mAttendees.get(position);
+        if (item.mDivider) {
+            return;
+        }
+        int status = item.mAttendee.mStatus;
+        if (status == Attendees.ATTENDEE_STATUS_ACCEPTED) {
+            mYes--;
+            if (mYes == 0) {
+                mDividers[0].mRemoved = true;
+            }
+        } else if (status == Attendees.ATTENDEE_STATUS_DECLINED) {
+            mNo--;
+            if (mNo == 0) {
+                mDividers[1].mRemoved = true;
+            }
+        } else if (status == Attendees.ATTENDEE_STATUS_TENTATIVE) {
+            mMaybe--;
+        } else {
+            mNoResponse--;
+        }
+        if ((mYes == 0 && mNo == 0 && mMaybe == 0) || mNoResponse == 0) {
+            mDividers[3].mRemoved = true;
+        }
+        mAttendees.remove(position);
+    }
+
+    public int findAttendeeByEmail(String email) {
+        int size = mAttendees.size();
+        for (int i = 0; i < size; i++) {
+            AttendeeItem item = mAttendees.get(i);
+            if (item.mDivider) {
+                continue;
+            }
+            if (TextUtils.equals(email, item.mAttendee.mEmail)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getCount() {
+        return mAttendees.size();
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return false;
+    }
+
+    @Override
+    public Attendee getItem(int position) {
+        return mAttendees.get(position).mAttendee;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        AttendeeItem item = mAttendees.get(position);
+
+        if (item.mDivider) {
+            TextView tv =  new TextView(mContext);
+            tv.setText(item.mDividerLabel);
+            tv.setTextAppearance(mContext, R.style.TextAppearance_EventInfo_Label);
+            tv.setVisibility(item.mRemoved ? View.GONE : View.VISIBLE);
+            return tv;
+        }
+
+        View v = mInflater.inflate(R.layout.contact_item, null);
+        Attendee attendee = item.mAttendee;
+
+        TextView nameView = (TextView)v.findViewById(R.id.name);
+        String name = attendee.mName;
+        if (name == null || name.length() == 0) {
+            name = attendee.mEmail;
+        }
+        nameView.setText(name);
+        if (item.mRemoved) {
+            nameView.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG | nameView.getPaintFlags());
+        }
+
+        ImageButton button = (ImageButton) v.findViewById(R.id.contact_remove);
+        button.setVisibility(View.VISIBLE);
+        button.setTag(item);
+        if (item.mRemoved) {
+            button.setImageResource(R.drawable.ic_btn_round_plus);
+        }
+        button.setOnClickListener(mRemoveListener);
+
+        QuickContactBadge badge = (QuickContactBadge)v.findViewById(R.id.badge);
+        badge.setImageDrawable(item.mBadge);
+        badge.assignContactFromEmail(item.mAttendee.mEmail, true);
+
+        if (item.mPresence != -1) {
+            ImageView presence = (ImageView) v.findViewById(R.id.presence);
+            presence.setImageResource(Presence.getPresenceIconResourceId(item.mPresence));
+            presence.setVisibility(View.VISIBLE);
+
+        }
+        return v;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return !mAttendees.get(position).mDivider;
+    }
+
+    /**
+     * Taken from com.google.android.gm.HtmlConversationActivity
+     *
+     * Send the intent that shows the Contact info corresponding to the email address.
+     */
+    public void showContactInfo(Attendee attendee, Rect rect) {
+        // First perform lookup query to find existing contact
+        final ContentResolver resolver = mContext.getContentResolver();
+        final String address = attendee.mEmail;
+        final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
+                Uri.encode(address));
+        final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
+
+        if (lookupUri != null) {
+            // Found matching contact, trigger QuickContact
+            QuickContact.showQuickContact(mContext, rect, lookupUri,
+                    QuickContact.MODE_MEDIUM, null);
+        } else {
+            // No matching contact, ask user to create one
+            final Uri mailUri = Uri.fromParts("mailto", address, null);
+            final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
+
+            // Pass along full E-mail string for possible create dialog
+            Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
+            intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
+
+            // Only provide personal name hint if we have one
+            final String senderPersonal = attendee.mName;
+            if (!TextUtils.isEmpty(senderPersonal)) {
+                intent.putExtra(Intents.Insert.NAME, senderPersonal);
+            }
+
+            mContext.startActivity(intent);
+        }
+    }
+
+    public boolean isRemoved(int position) {
+        return mAttendees.get(position).mRemoved;
+    }
+
+    // TODO put this into a Loader for auto-requeries
+    private class PresenceQueryHandler extends AsyncQueryHandler {
+
+        public PresenceQueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) {
+            if (cursor == null || cookie == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onQueryComplete: cursor=" + cursor + ", cookie=" + cookie);
+                }
+                return;
+            }
+            AttendeeItem item = (AttendeeItem)cookie;
+
+            try {
+                cursor.moveToPosition(-1);
+                boolean found = false;
+                int contactId = 0;
+                int photoId = 0;
+                int presence = 0;
+                while (cursor.moveToNext()) {
+                    String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX);
+                    int temp = 0;
+                    temp = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX);
+                    // A photo id must be > 0 and we only care about the contact
+                    // ID if there's a photo
+                    if (temp > 0) {
+                        photoId = temp;
+                        contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX);
+                    }
+                    // Take the most available status we can find.
+                    presence = Math.max(
+                            cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX), presence);
+
+                    found = true;
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                "onQueryComplete Id: " + contactId + " PhotoId: " + photoId
+                                        + " Email: " + email + " updateCount:" + item.mUpdateCounts
+                                        + " Presence:" + item.mPresence);
+                    }
+                }
+                if (found) {
+                    item.mPresence = presence;
+                    notifyDataSetChanged();
+
+                    if (photoId > 0 && item.mUpdateCounts < queryIndex) {
+                        item.mUpdateCounts = queryIndex;
+                        Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
+                                contactId);
+                        // Query for this contacts picture
+                        ContactsAsyncHelper.retrieveContactPhotoAsync(
+                                mContext, item, new Runnable() {
+                                    public void run() {
+                                        notifyDataSetChanged();
+                                    }
+                                }, personUri);
+                    }
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+}
diff --git a/src/com/android/calendar/event/EditEventFragment.java b/src/com/android/calendar/event/EditEventFragment.java
new file mode 100644
index 0000000..bef6920
--- /dev/null
+++ b/src/com/android/calendar/event/EditEventFragment.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.EventHandler;
+import com.android.calendar.CalendarController.EventInfo;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.CalendarEventModel;
+import com.android.calendar.CalendarEventModel.Attendee;
+import com.android.calendar.DeleteEventHelper;
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.text.TextUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+
+public class EditEventFragment extends Fragment implements EventHandler {
+    private static final String TAG = "EditEventActivity";
+
+    private static final boolean DEBUG = false;
+
+    private static final int TOKEN_EVENT = 0;
+    private static final int TOKEN_ATTENDEES = 1;
+    private static final int TOKEN_REMINDERS = 2;
+    private static final int TOKEN_CALENDARS = 3;
+
+    EditEventHelper mHelper;
+    CalendarEventModel mModel;
+    CalendarEventModel mOriginalModel;
+    EditEventView mView;
+    QueryHandler mHandler;
+
+    private AlertDialog mModifyDialog;
+    int mModification = Utils.MODIFY_UNINITIALIZED;
+
+    private EventInfo mEvent;
+    private Uri mUri;
+    private long mBegin;
+    private long mEnd;
+    private int mReturnView;
+
+    private Activity mContext;
+    private Done mOnDone = new Done();
+
+    private boolean mSaveOnDetach = true;
+
+    private InputMethodManager mInputMethodManager;
+
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            // If the query didn't return a cursor for some reason return
+            if (cursor == null) {
+                return;
+            }
+
+            // If the Activity is finishing, then close the cursor.
+            // Otherwise, use the new cursor in the adapter.
+            final Activity activity = EditEventFragment.this.getActivity();
+            if (activity == null || activity.isFinishing()) {
+                cursor.close();
+                return;
+            }
+            long eventId;
+            switch (token) {
+                case TOKEN_EVENT:
+                    if (cursor.getCount() == 0) {
+                        // The cursor is empty. This can happen if the event
+                        // was deleted.
+                        cursor.close();
+                        mContext.finish();
+                        return;
+                    }
+                    mOriginalModel = new CalendarEventModel();
+                    EditEventHelper.setModelFromCursor(mOriginalModel, cursor);
+                    EditEventHelper.setModelFromCursor(mModel, cursor);
+                    cursor.close();
+
+                    mOriginalModel.mUri = mUri;
+
+                    mModel.mUri = mUri;
+                    mModel.mOriginalStart = mBegin;
+                    mModel.mOriginalEnd = mEnd;
+                    mModel.mIsFirstEventInSeries = mBegin == mOriginalModel.mStart;
+                    mModel.mStart = mBegin;
+                    mModel.mEnd = mEnd;
+
+                    displayEditWhichDialogue();
+
+                    eventId = mModel.mId;
+                    // If there are attendees or alarms query for them
+                    // We only query one table at a time so that we can easily
+                    // tell if we are finished with all our queries. At a later
+                    // point we might want to parallelize this and keep track of
+                    // which queries are done.
+                    if (mModel.mHasAttendeeData && eventId != -1) {
+                        Uri attUri = Attendees.CONTENT_URI;
+                        String[] whereArgs = {
+                            Long.toString(eventId)
+                        };
+                        mHandler.startQuery(TOKEN_ATTENDEES, null, attUri,
+                                EditEventHelper.ATTENDEES_PROJECTION,
+                                EditEventHelper.ATTENDEES_WHERE /* selection */,
+                                whereArgs /* selection args */, null /* sort order */);
+                    } else if (mModel.mHasAlarm) {
+                        Uri rUri = Reminders.CONTENT_URI;
+                        String[] remArgs = {
+                                Long.toString(eventId), Integer.toString(Reminders.METHOD_ALERT),
+                                Integer.toString(Reminders.METHOD_DEFAULT)
+                        };
+                        mHandler
+                                .startQuery(TOKEN_REMINDERS, null, rUri,
+                                        EditEventHelper.REMINDERS_PROJECTION,
+                                        EditEventHelper.REMINDERS_WHERE /* selection */,
+                                        remArgs /* selection args */, null /* sort order */);
+                    } else {
+                        // Set the model if there are no more queries to
+                        // make
+                        mView.setModel(mModel);
+                    }
+                    break;
+                case TOKEN_ATTENDEES:
+                    try {
+                        while (cursor.moveToNext()) {
+                            String name = cursor.getString(EditEventHelper.ATTENDEES_INDEX_NAME);
+                            String email = cursor.getString(EditEventHelper.ATTENDEES_INDEX_EMAIL);
+                            int status = cursor.getInt(EditEventHelper.ATTENDEES_INDEX_STATUS);
+                            int relationship = cursor
+                                    .getInt(EditEventHelper.ATTENDEES_INDEX_RELATIONSHIP);
+                            if (email != null) {
+                                if (relationship == Attendees.RELATIONSHIP_ORGANIZER) {
+                                    mModel.mOrganizer = email;
+                                }
+                                if (mModel.mOwnerAccount != null &&
+                                        mModel.mOwnerAccount.equalsIgnoreCase(email)) {
+                                    int attendeeId =
+                                        cursor.getInt(EditEventHelper.ATTENDEES_INDEX_ID);
+                                    mModel.mOwnerAttendeeId = attendeeId;
+                                    mModel.mSelfAttendeeStatus = status;
+                                    mOriginalModel.mOwnerAttendeeId = attendeeId;
+                                    mOriginalModel.mSelfAttendeeStatus = status;
+                                    continue;
+                                }
+                            }
+                            Attendee attendee = new Attendee(name, email);
+                            attendee.mStatus = status;
+                            mModel.addAttendee(attendee);
+                            mOriginalModel.addAttendee(attendee);
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                    // This is done after attendees so we know when our
+                    // model is filled out
+                    eventId = mModel.mId;
+                    boolean hasAlarm = mModel.mHasAlarm;
+                    if (hasAlarm) {
+                        Uri rUri = Reminders.CONTENT_URI;
+                        String[] remArgs = {
+                                Long.toString(eventId), Integer.toString(Reminders.METHOD_ALERT),
+                                Integer.toString(Reminders.METHOD_DEFAULT)
+                        };
+                        mHandler
+                                .startQuery(TOKEN_REMINDERS, null, rUri,
+                                        EditEventHelper.REMINDERS_PROJECTION,
+                                        EditEventHelper.REMINDERS_WHERE /* selection */,
+                                        remArgs /* selection args */, null /* sort order */);
+                    } else {
+                        // Set the model if there are no more queries to
+                        // make
+                        mView.setModel(mModel);
+                    }
+                    break;
+                case TOKEN_REMINDERS:
+                    try {
+                        // Add all reminders to the models
+                        while (cursor.moveToNext()) {
+                            int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
+                            mModel.mReminderMinutes.add(minutes);
+                            mOriginalModel.mReminderMinutes.add(minutes);
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                    // Set the model after we finish all the necessary
+                    // queries.
+                    mView.setModel(mModel);
+                    break;
+                case TOKEN_CALENDARS:
+                    MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
+
+                    if (DEBUG) {
+                        Log.d(TAG, "onQueryComplete: setting cursor with "
+                                + matrixCursor.getCount() + " calendars");
+                    }
+                    mView.setCalendarsCursor(matrixCursor, isAdded() && isResumed());
+                    cursor.close();
+                    break;
+            }
+        }
+    }
+
+    public EditEventFragment() {
+        mEvent = null;
+    }
+
+    public EditEventFragment(EventInfo event, int returnView) {
+        mEvent = event;
+        mReturnView = returnView;
+    }
+
+    private void startQuery() {
+        mUri = null;
+        mBegin = -1;
+        mEnd = -1;
+        if (mEvent != null) {
+            if (mEvent.id != -1) {
+                mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEvent.id);
+            }
+            if (mEvent.startTime != null) {
+                mBegin = mEvent.startTime.toMillis(true);
+            }
+            if (mEvent.endTime != null) {
+                mEnd = mEvent.endTime.toMillis(true);
+            }
+        }
+
+        if (mBegin <= 0) {
+            // use a default value instead
+            mBegin = mHelper.constructDefaultStartTime(System.currentTimeMillis());
+        }
+        if (mEnd < mBegin) {
+            // use a default value instead
+            mEnd = mHelper.constructDefaultEndTime(mBegin);
+        }
+
+        // Kick off the query for the event
+        boolean newEvent = mUri == null;
+        if (!newEvent) {
+            if (DEBUG) {
+                Log.d(TAG, "onCreate: uri for event is " + mUri.toString());
+            }
+            mHandler.startQuery(TOKEN_EVENT, null, mUri, EditEventHelper.EVENT_PROJECTION,
+                    null /* selection */, null /* selection args */, null /* sort order */);
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "onCreate: Editing a new event.");
+            }
+            mModel.mStart = mBegin;
+            mModel.mEnd = mEnd;
+            mModel.mSelfAttendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED;
+            mView.setModel(mModel);
+
+            // Start a query in the background to read the list of calendars
+            mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI,
+                    EditEventHelper.CALENDARS_PROJECTION,
+                    EditEventHelper.CALENDARS_WHERE_WRITEABLE_VISIBLE, null /* selection args */,
+                    null /* sort order */);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+
+        mHelper = new EditEventHelper(activity, null);
+        mHandler = new QueryHandler(activity.getContentResolver());
+        mModel = new CalendarEventModel(activity);
+        mInputMethodManager = (InputMethodManager)
+                activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+//        mContext.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        View view = inflater.inflate(R.layout.edit_event, null);
+        mView = new EditEventView(mContext, view, mOnDone);
+        startQuery();
+        return view;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+    }
+//
+//    @Override
+//    public boolean onCreateOptionsMenu(Menu menu) {
+//        MenuItem item;
+//        item = menu.add(MENU_GROUP_ADD_REMINDER, MENU_ADD_REMINDER, 0, R.string.add_new_reminder);
+//        item.setIcon(R.drawable.ic_menu_reminder);
+//        item.setAlphabeticShortcut('r');
+//
+//        return super.onCreateOptionsMenu(menu);
+//    }
+//
+//    @Override
+//    public boolean onPrepareOptionsMenu(Menu menu) {
+//        int numReminders = mView.getReminderCount();
+//        if (numReminders < EditEventHelper.MAX_REMINDERS) {
+//            menu.setGroupEnabled(MENU_GROUP_ADD_REMINDER, true);
+//            menu.setGroupVisible(MENU_GROUP_ADD_REMINDER, true);
+//        } else {
+//            menu.setGroupEnabled(MENU_GROUP_ADD_REMINDER, false);
+//            menu.setGroupVisible(MENU_GROUP_ADD_REMINDER, false);
+//        }
+//
+//        return super.onPrepareOptionsMenu(menu);
+//    }
+//
+//    @Override
+//    public boolean onOptionsItemSelected(MenuItem item) {
+//        switch (item.getItemId()) {
+//            case MENU_ADD_REMINDER:
+//                mView.addReminder();
+//                return true;
+//        }
+//        return super.onOptionsItemSelected(item);
+//    }
+//
+    protected void displayEditWhichDialogue() {
+        if (!TextUtils.isEmpty(mModel.mRrule) && mModification == Utils.MODIFY_UNINITIALIZED) {
+            // If this event has not been synced, then don't allow deleting
+            // or changing a single instance.
+            String mSyncId = mModel.mSyncId;
+            boolean isFirstEventInSeries = mModel.mIsFirstEventInSeries;
+
+            // If we haven't synced this repeating event yet, then don't
+            // allow the user to change just one instance.
+            int itemIndex = 0;
+            CharSequence[] items;
+            if (mSyncId == null) {
+                if (isFirstEventInSeries) {
+                    // Still display the option so the user knows all events are
+                    // changing
+                    items = new CharSequence[1];
+                } else {
+                    items = new CharSequence[2];
+                }
+            } else {
+                if (isFirstEventInSeries) {
+                    items = new CharSequence[2];
+                } else {
+                    items = new CharSequence[3];
+                }
+                items[itemIndex++] = mContext.getText(R.string.modify_event);
+            }
+            items[itemIndex++] = mContext.getText(R.string.modify_all);
+
+            // Do one more check to make sure this remains at the end of the list
+            if (!isFirstEventInSeries) {
+                items[itemIndex++] = mContext.getText(R.string.modify_all_following);
+            }
+
+            // Display the modification dialog.
+            if (mModifyDialog != null) {
+                mModifyDialog.dismiss();
+                mModifyDialog = null;
+            }
+            mModifyDialog = new AlertDialog.Builder(mContext).setOnCancelListener(
+                    new OnCancelListener() {
+                public void onCancel(DialogInterface dialog) {
+                    mContext.finish();
+                }
+            }).setTitle(R.string.edit_event_label).setItems(items, new OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    if (which == 0) {
+                        mModification = (mModel.mSyncId == null) ? Utils.MODIFY_ALL
+                                : Utils.MODIFY_SELECTED;
+                    } else if (which == 1) {
+                        mModification = (mModel.mSyncId == null) ? Utils.MODIFY_ALL_FOLLOWING
+                                : Utils.MODIFY_ALL;
+                    } else if (which == 2) {
+                        mModification = Utils.MODIFY_ALL_FOLLOWING;
+                    }
+
+                    mView.setModification(mModification);
+                }
+            }).show();
+        }
+    }
+
+    class Done implements EditEventHelper.EditDoneRunnable {
+        private int mCode = -1;
+
+        public void setDoneCode(int code) {
+            mCode = code;
+        }
+
+        public void run() {
+            Time start = null;
+            // We only want this to get called once, either because the user
+            // pressed back/home or one of the buttons on screen
+            mSaveOnDetach = false;
+
+            if ((mCode & Utils.DONE_SAVE) != 0) {
+                if (mModel != null && !mModel.equals(mOriginalModel)) {
+                    if (mHelper.saveEvent(mModel, mOriginalModel, mModification)) {
+                        if (mModel.mUri != null) {
+                            Toast.makeText(mContext, R.string.saving_event, Toast.LENGTH_SHORT)
+                                    .show();
+                        } else {
+                            Toast.makeText(mContext, R.string.creating_event,
+                                    Toast.LENGTH_SHORT).show();
+                        }
+                    }
+                }
+            }
+
+            if ((mCode & Utils.DONE_DELETE) != 0) {
+                long begin = mModel.mStart;
+                long end = mModel.mEnd;
+                int which = -1;
+                switch (mModification) {
+                    case Utils.MODIFY_SELECTED:
+                        which = DeleteEventHelper.DELETE_SELECTED;
+                        break;
+                    case Utils.MODIFY_ALL_FOLLOWING:
+                        which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
+                        break;
+                    case Utils.MODIFY_ALL:
+                        which = DeleteEventHelper.DELETE_ALL;
+                        break;
+                }
+                DeleteEventHelper deleteHelper = new DeleteEventHelper(mContext, mContext,
+                        false /* exitWhenDone */);
+                // TODO update delete helper to use the model instead of the cursor
+                deleteHelper.delete(begin, end, mModel, which);
+            }
+
+            if ((mCode & Utils.DONE_EXIT) != 0) {
+                // This will exit the edit event screen, should be called
+                // when we want to return to the main calendar views
+                if (mModel != null) {
+                    start = new Time();
+                    start.set(mModel.mStart);
+                }
+                CalendarController controller = CalendarController.getInstance(mContext);
+                controller.sendEvent(EditEventFragment.this, EventType.GO_TO, start, null, -1,
+                        mReturnView);
+            }
+
+            // Hide a software keyboard so that user won't see it even after this Fragment's
+            // disappearing.
+            final View focusedView = mContext.getCurrentFocus();
+            if (focusedView != null) {
+                mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
+                focusedView.clearFocus();
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mView != null) {
+            mView.setModel(null);
+        }
+        if (mModifyDialog != null) {
+            mModifyDialog.dismiss();
+            mModifyDialog = null;
+        }
+
+        super.onDestroy();
+    }
+
+    @Override
+    public void eventsChanged() {
+        // TODO Requery to see if event has changed
+    }
+
+    @Override
+    public boolean getAllDay() {
+        return false;
+    }
+
+    @Override
+    public long getSelectedTime() {
+        return mBegin;
+    }
+
+    @Override
+    public long getSupportedEventTypes() {
+        return EventType.USER_HOME;
+    }
+
+    @Override
+    public void goTo(Time time, boolean animate) {
+    }
+
+    @Override
+    public void goToToday() {
+    }
+
+    @Override
+    public void handleEvent(EventInfo event) {
+        // It's currently unclear if we want to save the event or not when home
+        // is pressed. When creating a new event we shouldn't save since we
+        // can't get the id of the new event easily.
+        if ((false && event.eventType == EventType.USER_HOME) || (event.eventType == EventType.GO_TO
+                && mSaveOnDetach)) {
+            if (mView != null && mView.prepareForSave()) {
+                mOnDone.setDoneCode(Utils.DONE_SAVE);
+                mOnDone.run();
+            }
+        }
+    }
+}
diff --git a/src/com/android/calendar/event/EditEventHelper.java b/src/com/android/calendar/event/EditEventHelper.java
new file mode 100644
index 0000000..7e20bf1
--- /dev/null
+++ b/src/com/android/calendar/event/EditEventHelper.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.AsyncQueryService;
+import com.android.calendar.CalendarEventModel;
+import com.android.calendar.CalendarEventModel.Attendee;
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+import com.android.common.Rfc822Validator;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.pim.EventRecurrence;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.QuickContactBadge;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.TimeZone;
+
+public class EditEventHelper {
+    private static final String TAG = "EditEventHelper";
+
+    private static final boolean DEBUG = false;
+
+    public static final int MAX_REMINDERS = 5;
+
+    public static final String[] EVENT_PROJECTION = new String[] {
+            Events._ID, // 0
+            Events.TITLE, // 1
+            Events.DESCRIPTION, // 2
+            Events.EVENT_LOCATION, // 3
+            Events.ALL_DAY, // 4
+            Events.HAS_ALARM, // 5
+            Events.CALENDAR_ID, // 6
+            Events.DTSTART, // 7
+            Events.DTEND, // 8
+            Events.DURATION, // 9
+            Events.EVENT_TIMEZONE, // 10
+            Events.RRULE, // 11
+            Events._SYNC_ID, // 12
+            Events.TRANSPARENCY, // 13
+            Events.VISIBILITY, // 14
+            Events.OWNER_ACCOUNT, // 15
+            Events.HAS_ATTENDEE_DATA, // 16
+            Events.ORIGINAL_EVENT, // 17
+    };
+    protected static final int EVENT_INDEX_ID = 0;
+    protected static final int EVENT_INDEX_TITLE = 1;
+    protected static final int EVENT_INDEX_DESCRIPTION = 2;
+    protected static final int EVENT_INDEX_EVENT_LOCATION = 3;
+    protected static final int EVENT_INDEX_ALL_DAY = 4;
+    protected static final int EVENT_INDEX_HAS_ALARM = 5;
+    protected static final int EVENT_INDEX_CALENDAR_ID = 6;
+    protected static final int EVENT_INDEX_DTSTART = 7;
+    protected static final int EVENT_INDEX_DTEND = 8;
+    protected static final int EVENT_INDEX_DURATION = 9;
+    protected static final int EVENT_INDEX_TIMEZONE = 10;
+    protected static final int EVENT_INDEX_RRULE = 11;
+    protected static final int EVENT_INDEX_SYNC_ID = 12;
+    protected static final int EVENT_INDEX_TRANSPARENCY = 13;
+    protected static final int EVENT_INDEX_VISIBILITY = 14;
+    protected static final int EVENT_INDEX_OWNER_ACCOUNT = 15;
+    protected static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 16;
+    protected static final int EVENT_INDEX_ORIGINAL_EVENT = 17;
+
+    public static final String[] REMINDERS_PROJECTION = new String[] {
+            Reminders._ID, // 0
+            Reminders.MINUTES, // 1
+            Reminders.METHOD, // 2
+    };
+    public static final int REMINDERS_INDEX_MINUTES = 1;
+    public static final int REMINDERS_INDEX_METHOD = 2;
+    public static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=? AND (" + Reminders.METHOD
+            + "=?" + " OR " + Reminders.METHOD + "=?" + ")";
+
+    // Visible for testing
+    static final String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND "
+            + Attendees.ATTENDEE_EMAIL + " IN (";
+
+    public static final int DOES_NOT_REPEAT = 0;
+    public static final int REPEATS_DAILY = 1;
+    public static final int REPEATS_EVERY_WEEKDAY = 2;
+    public static final int REPEATS_WEEKLY_ON_DAY = 3;
+    public static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
+    public static final int REPEATS_MONTHLY_ON_DAY = 5;
+    public static final int REPEATS_YEARLY = 6;
+    public static final int REPEATS_CUSTOM = 7;
+
+    protected static final int MODIFY_UNINITIALIZED = 0;
+    protected static final int MODIFY_SELECTED = 1;
+    protected static final int MODIFY_ALL_FOLLOWING = 2;
+    protected static final int MODIFY_ALL = 3;
+
+    protected static final int DAY_IN_SECONDS = 24 * 60 * 60;
+
+    private Context mContext;
+    private AsyncQueryService mService;
+
+    // public int mModification;
+    private Rfc822Validator mEmailValidator;
+
+    // This allows us to flag the event if something is wrong with it, right now
+    // if an uri is provided for an event that doesn't exist in the db.
+    protected boolean mEventOk = true;
+
+    public static final int ATTENDEE_NO_RESPONSE = -1;
+    public static final int ATTENDEE_ID_NONE = -1;
+    public static final int[] ATTENDEE_VALUES = {
+            ATTENDEE_NO_RESPONSE,
+            Attendees.ATTENDEE_STATUS_ACCEPTED,
+            Attendees.ATTENDEE_STATUS_TENTATIVE,
+            Attendees.ATTENDEE_STATUS_DECLINED,
+    };
+
+    /**
+     * This is the symbolic name for the key used to pass in the boolean for
+     * creating all-day events that is part of the extra data of the intent.
+     * This is used only for creating new events and is set to true if the
+     * default for the new event should be an all-day event.
+     */
+    public static final String EVENT_ALL_DAY = "allDay";
+
+    static final String[] CALENDARS_PROJECTION = new String[] {
+            Calendars._ID, // 0
+            Calendars.DISPLAY_NAME, // 1
+            Calendars.OWNER_ACCOUNT, // 2
+            Calendars.COLOR, // 3
+    };
+    static final int CALENDARS_INDEX_ID = 0;
+    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
+    static final int CALENDARS_INDEX_COLOR = 3;
+
+    static final String CALENDARS_WHERE_WRITEABLE_VISIBLE = Calendars.ACCESS_LEVEL + ">="
+            + Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SELECTED + "=1";
+
+    static final String[] ATTENDEES_PROJECTION = new String[] {
+            Attendees._ID, // 0
+            Attendees.ATTENDEE_NAME, // 1
+            Attendees.ATTENDEE_EMAIL, // 2
+            Attendees.ATTENDEE_RELATIONSHIP, // 3
+            Attendees.ATTENDEE_STATUS, // 4
+    };
+    static final int ATTENDEES_INDEX_ID = 0;
+    static final int ATTENDEES_INDEX_NAME = 1;
+    static final int ATTENDEES_INDEX_EMAIL = 2;
+    static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
+    static final int ATTENDEES_INDEX_STATUS = 4;
+    static final String ATTENDEES_WHERE_NOT_ORGANIZER = Attendees.EVENT_ID + "=? AND "
+            + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
+    static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
+
+    public static class ContactViewHolder {
+        QuickContactBadge badge;
+        ImageView presence;
+        int updateCounts;
+    }
+
+    public static class AttendeeItem {
+        public boolean mRemoved;
+        public boolean mDivider;
+        public String mDividerLabel;
+        public Attendee mAttendee;
+        public Drawable mBadge;
+        public int mPresence;
+        public int mUpdateCounts;
+    }
+
+    public EditEventHelper(Context context, CalendarEventModel model) {
+        mContext = context;
+        mService = new AsyncQueryService(context);
+        setDomainFromModel(model);
+    }
+
+    // Sets up the email validator for the given model
+    public void setDomainFromModel(CalendarEventModel model) {
+        String domain = "gmail.com";
+        if (model != null) {
+            String ownerAccount = model.mOwnerAccount;
+            if (!TextUtils.isEmpty(ownerAccount)) {
+                String ownerDomain = extractDomain(ownerAccount);
+                if (!TextUtils.isEmpty(ownerDomain)) {
+                    domain = ownerDomain;
+                }
+            }
+        }
+        mEmailValidator = new Rfc822Validator(domain);
+    }
+
+    /**
+     * Saves the event. Returns true if the event was successfully saved, false
+     * otherwise.
+     *
+     * @param model The event model to save
+     * @param originalModel A model of the original event if it exists
+     * @param modifyWhich For recurring events which type of series modification to use
+     * @return true if the event was successfully queued for saving
+     */
+    public boolean saveEvent(CalendarEventModel model, CalendarEventModel originalModel,
+            int modifyWhich) {
+        boolean forceSaveReminders = false;
+
+        if (DEBUG) {
+            Log.d(TAG, "Saving event model: " + model);
+        }
+
+        if (!mEventOk) {
+            if (DEBUG) {
+                Log.w(TAG, "Event no longer exists. Event was not saved.");
+            }
+            return false;
+        }
+
+        // It's a problem if we try to save a non-existent or invalid model or if we're
+        // modifying an existing event and we have the wrong original model
+        if (model == null) {
+            Log.e(TAG, "Attempted to save null model.");
+            return false;
+        }
+        if (!model.isValid()) {
+            Log.e(TAG, "Attempted to save invalid model.");
+            return false;
+        }
+        if (originalModel != null && !isSameEvent(model, originalModel)) {
+            Log.e(TAG, "Attempted to update existing event but models didn't refer to the same "
+                    + "event.");
+            return false;
+        }
+
+        // TODO Check if anything has been changed and return false if it
+        // hasn't.
+
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        int eventIdIndex = -1;
+
+        ContentValues values = getContentValuesFromModel(model);
+        Uri uri = model.mUri;
+
+        if (uri != null && originalModel == null) {
+            Log.e(TAG, "Existing event but no originalModel provided. Aborting save.");
+            return false;
+        }
+
+        // Update the "hasAlarm" field for the event
+        ArrayList<Integer> reminderMinutes = model.mReminderMinutes;
+        int len = reminderMinutes.size();
+        values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
+
+        if (uri == null) {
+            // Add hasAttendeeData for a new event
+            values.put(Events.HAS_ATTENDEE_DATA, 1);
+            eventIdIndex = ops.size();
+            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
+                    Events.CONTENT_URI).withValues(values);
+            ops.add(b.build());
+            forceSaveReminders = true;
+
+        } else if (model.mRrule == null && originalModel.mRrule == null) {
+            // Simple update to a non-recurring event
+            checkTimeDependentFields(originalModel, model, values, modifyWhich);
+            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+
+        } else if (originalModel.mRrule == null) {
+            // This event was changed from a non-repeating event to a
+            // repeating event.
+            ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+
+        } else if (modifyWhich == MODIFY_SELECTED) {
+            // Modify contents of the current instance of repeating event
+            // Create a recurrence exception
+            long begin = model.mOriginalStart;
+            values.put(Events.ORIGINAL_EVENT, originalModel.mSyncId);
+            values.put(Events.ORIGINAL_INSTANCE_TIME, begin);
+            boolean allDay = originalModel.mAllDay;
+            values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
+
+            eventIdIndex = ops.size();
+            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
+                    Events.CONTENT_URI).withValues(values);
+            ops.add(b.build());
+            forceSaveReminders = true;
+
+        } else if (modifyWhich == MODIFY_ALL_FOLLOWING) {
+
+            if (model.mRrule == null) {
+                // We've changed a recurring event to a non-recurring event.
+                // If the event we are editing is the first in the series,
+                // then delete the whole series. Otherwise, update the series
+                // to end at the new start time.
+                if (isFirstEventInSeries(model, originalModel)) {
+                    ops.add(ContentProviderOperation.newDelete(uri).build());
+                } else {
+                    // Update the current repeating event to end at the new
+                    // start time.
+                    updatePastEvents(ops, originalModel, model.mOriginalStart);
+                }
+                eventIdIndex = ops.size();
+                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
+                        .build());
+            } else {
+                if (isFirstEventInSeries(model, originalModel)) {
+                    checkTimeDependentFields(originalModel, model, values, modifyWhich);
+                    ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(uri)
+                            .withValues(values);
+                    ops.add(b.build());
+                } else {
+                    // Update the current repeating event to end at the new
+                    // start time.
+                    updatePastEvents(ops, originalModel, model.mOriginalStart);
+
+                    // Create a new event with the user-modified fields
+                    eventIdIndex = ops.size();
+                    ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(
+                            values).build());
+                }
+            }
+            forceSaveReminders = true;
+
+        } else if (modifyWhich == MODIFY_ALL) {
+
+            // Modify all instances of repeating event
+            if (model.mRrule == null) {
+                // We've changed a recurring event to a non-recurring event.
+                // Delete the whole series and replace it with a new
+                // non-recurring event.
+                ops.add(ContentProviderOperation.newDelete(uri).build());
+
+                eventIdIndex = ops.size();
+                ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
+                        .build());
+                forceSaveReminders = true;
+            } else {
+                checkTimeDependentFields(originalModel, model, values, modifyWhich);
+                ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+            }
+        }
+
+        // New Event or New Exception to an existing event
+        boolean newEvent = (eventIdIndex != -1);
+        ArrayList<Integer> originalMinutes;
+        if (originalModel != null) {
+            originalMinutes = originalModel.mReminderMinutes;
+        } else {
+            originalMinutes = new ArrayList<Integer>();
+        }
+
+        if (newEvent) {
+            saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, originalMinutes,
+                    forceSaveReminders);
+        } else if (uri != null) {
+            long eventId = ContentUris.parseId(uri);
+            saveReminders(ops, eventId, reminderMinutes, originalMinutes, forceSaveReminders);
+        }
+
+        ContentProviderOperation.Builder b;
+        boolean hasAttendeeData = model.mHasAttendeeData;
+
+        // New event/instance - Set Organizer's response as yes
+        if (hasAttendeeData && newEvent) {
+            values.clear();
+
+            String ownerEmail = model.mOwnerAccount;
+            if (ownerEmail != null) {
+                values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
+                values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
+                values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+                values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus);
+
+                b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(values);
+                b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
+                ops.add(b.build());
+            }
+        } else if (hasAttendeeData &&
+                model.mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus &&
+                model.mOwnerAttendeeId != -1) {
+            if (DEBUG) {
+                Log.d(TAG, "Setting attendee status to " + model.mSelfAttendeeStatus);
+            }
+            Uri attUri = ContentUris.withAppendedId(Attendees.CONTENT_URI, model.mOwnerAttendeeId);
+
+            values.clear();
+            values.put(Attendees.ATTENDEE_STATUS, model.mSelfAttendeeStatus);
+            values.put(Attendees.EVENT_ID, model.mId);
+            b = ContentProviderOperation.newUpdate(attUri).withValues(values);
+            ops.add(b.build());
+        }
+
+        // TODO: is this the right test? this currently checks if this is
+        // a new event or an existing event. or is this a paranoia check?
+        if (hasAttendeeData && (newEvent || uri != null)) {
+            String attendees = model.getAttendeesString();
+            String originalAttendeesString;
+            if (originalModel != null) {
+                originalAttendeesString = originalModel.getAttendeesString();
+            } else {
+                originalAttendeesString = "";
+            }
+            // Hit the content provider only if this is a new event or the user
+            // has changed it
+            if (newEvent || !TextUtils.equals(originalAttendeesString, attendees)) {
+                // figure out which attendees need to be added and which ones
+                // need to be deleted. use a linked hash set, so we maintain
+                // order (but also remove duplicates).
+                setDomainFromModel(model);
+                HashMap<String, Attendee> newAttendees = model.mAttendeesList;
+                LinkedList<String> removedAttendees = new LinkedList<String>();
+
+                // the eventId is only used if eventIdIndex is -1.
+                // TODO: clean up this code.
+                long eventId = uri != null ? ContentUris.parseId(uri) : -1;
+
+                // only compute deltas if this is an existing event.
+                // new events (being inserted into the Events table) won't
+                // have any existing attendees.
+                if (!newEvent) {
+                    removedAttendees.clear();
+                    HashMap<String, Attendee> originalAttendees = originalModel.mAttendeesList;
+                    for (String originalEmail : originalAttendees.keySet()) {
+                        if (newAttendees.containsKey(originalEmail)) {
+                            // existing attendee. remove from new attendees set.
+                            newAttendees.remove(originalEmail);
+                        } else {
+                            // no longer in attendees. mark as removed.
+                            removedAttendees.add(originalEmail);
+                        }
+                    }
+
+                    // delete removed attendees if necessary
+                    if (removedAttendees.size() > 0) {
+                        b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
+
+                        String[] args = new String[removedAttendees.size() + 1];
+                        args[0] = Long.toString(eventId);
+                        int i = 1;
+                        StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX);
+                        for (String removedAttendee : removedAttendees) {
+                            if (i > 1) {
+                                deleteWhere.append(",");
+                            }
+                            deleteWhere.append("?");
+                            args[i++] = removedAttendee;
+                        }
+                        deleteWhere.append(")");
+                        b.withSelection(deleteWhere.toString(), args);
+                        ops.add(b.build());
+                    }
+                }
+
+                if (newAttendees.size() > 0) {
+                    // Insert the new attendees
+                    for (Attendee attendee : newAttendees.values()) {
+                        values.clear();
+                        values.put(Attendees.ATTENDEE_NAME, attendee.mName);
+                        values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail);
+                        values.put(Attendees.ATTENDEE_RELATIONSHIP,
+                                Attendees.RELATIONSHIP_ATTENDEE);
+                        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+                        values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+
+                        if (newEvent) {
+                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
+                                    .withValues(values);
+                            b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
+                        } else {
+                            values.put(Attendees.EVENT_ID, eventId);
+                            b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
+                                    .withValues(values);
+                        }
+                        ops.add(b.build());
+                    }
+                }
+            }
+        }
+
+
+        mService.startBatch(mService.getNextToken(), null, android.provider.Calendar.AUTHORITY, ops,
+                Utils.UNDO_DELAY);
+
+        return true;
+    }
+
+    public static LinkedHashSet<Rfc822Token> getAddressesFromList(String list,
+            Rfc822Validator validator) {
+        LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>();
+        Rfc822Tokenizer.tokenize(list, addresses);
+        if (validator == null) {
+            return addresses;
+        }
+
+        // validate the emails, out of paranoia. they should already be
+        // validated on input, but drop any invalid emails just to be safe.
+        Iterator<Rfc822Token> addressIterator = addresses.iterator();
+        while (addressIterator.hasNext()) {
+            Rfc822Token address = addressIterator.next();
+            if (!validator.isValid(address.getAddress())) {
+                Log.v(TAG, "Dropping invalid attendee email address: " + address.getAddress());
+                addressIterator.remove();
+            }
+        }
+        return addresses;
+    }
+
+    /**
+     * When we aren't given an explicit start time, we default to the next
+     * upcoming half hour. So, for example, 5:01 -> 5:30, 5:30 -> 6:00, etc.
+     *
+     * @return a UTC time in milliseconds representing the next upcoming half
+     * hour
+     */
+    protected long constructDefaultStartTime(long now) {
+        Time defaultStart = new Time();
+        defaultStart.set(now);
+        defaultStart.second = 0;
+        defaultStart.minute = 30;
+        long defaultStartMillis = defaultStart.toMillis(false);
+        if (now < defaultStartMillis) {
+            return defaultStartMillis;
+        } else {
+            return defaultStartMillis + 30 * DateUtils.MINUTE_IN_MILLIS;
+        }
+    }
+
+    /**
+     * When we aren't given an explicit end time, we default to an hour after
+     * the start time.
+     * @param startTime the start time
+     * @return a default end time
+     */
+    protected long constructDefaultEndTime(long startTime) {
+        return startTime + DateUtils.HOUR_IN_MILLIS;
+    }
+
+    // TODO think about how useful this is. Probably check if our event has
+    // changed early on and either update all or nothing. Should still do the if
+    // MODIFY_ALL bit.
+    void checkTimeDependentFields(CalendarEventModel originalModel, CalendarEventModel model,
+            ContentValues values, int modifyWhich) {
+        long oldBegin = model.mOriginalStart;
+        long oldEnd = model.mOriginalEnd;
+        boolean oldAllDay = originalModel.mAllDay;
+        String oldRrule = originalModel.mRrule;
+        String oldTimezone = originalModel.mTimezone;
+
+        long newBegin = model.mStart;
+        long newEnd = model.mEnd;
+        boolean newAllDay = model.mAllDay;
+        String newRrule = model.mRrule;
+        String newTimezone = model.mTimezone;
+
+        // If none of the time-dependent fields changed, then remove them.
+        if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
+                && TextUtils.equals(oldRrule, newRrule)
+                && TextUtils.equals(oldTimezone, newTimezone)) {
+            values.remove(Events.DTSTART);
+            values.remove(Events.DTEND);
+            values.remove(Events.DURATION);
+            values.remove(Events.ALL_DAY);
+            values.remove(Events.RRULE);
+            values.remove(Events.EVENT_TIMEZONE);
+            return;
+        }
+
+        if (oldRrule == null || newRrule == null) {
+            return;
+        }
+
+        // If we are modifying all events then we need to set DTSTART to the
+        // start time of the first event in the series, not the current
+        // date and time. If the start time of the event was changed
+        // (from, say, 3pm to 4pm), then we want to add the time difference
+        // to the start time of the first event in the series (the DTSTART
+        // value). If we are modifying one instance or all following instances,
+        // then we leave the DTSTART field alone.
+        if (modifyWhich == MODIFY_ALL) {
+            long oldStartMillis = originalModel.mStart;
+            long oldStartMillis2 = oldStartMillis;
+            if (oldBegin != newBegin) {
+                // The user changed the start time of this event
+                long offset = newBegin - oldBegin;
+                oldStartMillis += offset;
+            }
+            if (newAllDay) {
+                Time time = new Time(Time.TIMEZONE_UTC);
+                time.set(oldStartMillis);
+                time.hour = 0;
+                time.minute = 0;
+                time.second = 0;
+                oldStartMillis = time.toMillis(false);
+            }
+            values.put(Events.DTSTART, oldStartMillis);
+        }
+    }
+
+    /**
+     * Prepares an update to the original event so it stops where the new series
+     * begins When we update 'this and all following' events we need to change
+     * the original event to end before a new series starts. This creates an
+     * update to the old event's rrule to do that.
+     *
+     * @param ops The list of operations to add the update to
+     * @param originalModel The original event that we're updating
+     * @param initialBeginTime The original start time for the exception
+     */
+    public void updatePastEvents(ArrayList<ContentProviderOperation> ops,
+            CalendarEventModel originalModel, long initialBeginTime) {
+        boolean allDay = originalModel.mAllDay;
+        String oldRrule = originalModel.mRrule;
+
+        EventRecurrence eventRecurrence = new EventRecurrence();
+        eventRecurrence.parse(oldRrule);
+
+        Time untilTime = new Time();
+        Time dtstart = new Time();
+        long begin = initialBeginTime;
+        ContentValues oldValues = new ContentValues();
+
+        // The "until" time must be in UTC time in order for Google calendar
+        // to display it properly. For all-day events, the "until" time string
+        // must include just the date field, and not the time field. The
+        // repeating events repeat up to and including the "until" time.
+        untilTime.timezone = Time.TIMEZONE_UTC;
+        dtstart.timezone = originalModel.mTimezone;
+        dtstart.set(originalModel.mStart);
+
+        // Subtract one second from the old begin time to get the new
+        // "until" time.
+        untilTime.set(begin - 1000); // subtract one second (1000 millis)
+        if (allDay) {
+            untilTime.hour = 0;
+            untilTime.minute = 0;
+            untilTime.second = 0;
+            untilTime.allDay = true;
+            untilTime.normalize(false);
+
+            dtstart.hour = 0;
+            dtstart.minute = 0;
+            dtstart.second = 0;
+            dtstart.allDay = true;
+            dtstart.timezone = Time.TIMEZONE_UTC;
+        }
+        eventRecurrence.until = untilTime.format2445();
+
+        oldValues.put(Events.RRULE, eventRecurrence.toString());
+        oldValues.put(Events.DTSTART, dtstart.normalize(true));
+        ContentProviderOperation.Builder b = ContentProviderOperation.newUpdate(originalModel.mUri)
+                .withValues(oldValues);
+        ops.add(b.build());
+    }
+
+    // Constructs a label given an arbitrary number of minutes. For example,
+    // if the given minutes is 63, then this returns the string "63 minutes".
+    // As another example, if the given minutes is 120, then this returns
+    // "2 hours".
+    public String constructReminderLabel(int minutes, boolean abbrev) {
+        Resources resources = mContext.getResources();
+        int value, resId;
+
+        if (minutes % 60 != 0) {
+            value = minutes;
+            if (abbrev) {
+                resId = R.plurals.Nmins;
+            } else {
+                resId = R.plurals.Nminutes;
+            }
+        } else if (minutes % (24 * 60) != 0) {
+            value = minutes / 60;
+            resId = R.plurals.Nhours;
+        } else {
+            value = minutes / (24 * 60);
+            resId = R.plurals.Ndays;
+        }
+
+        String format = resources.getQuantityString(resId, value);
+        return String.format(format, value);
+    }
+
+    /**
+     * Compares two models to ensure that they refer to the same event. This is
+     * a safety check to make sure an updated event model refers to the same
+     * event as the original model. If the original model is null then this is a
+     * new event or we're forcing an overwrite so we return true in that case.
+     * The important identifiers are the Calendar Id and the Event Id.
+     *
+     * @return
+     */
+    public static boolean isSameEvent(CalendarEventModel model, CalendarEventModel originalModel) {
+        if (originalModel == null) {
+            return true;
+        }
+
+        if (model.mCalendarId != originalModel.mCalendarId) {
+            return false;
+        }
+        if (model.mId != originalModel.mId) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Saves the reminders, if they changed. Returns true if operations to
+     * update the database were added.
+     *
+     * @param ops the array of ContentProviderOperations
+     * @param eventId the id of the event whose reminders are being updated
+     * @param reminderMinutes the array of reminders set by the user
+     * @param originalMinutes the original array of reminders
+     * @param forceSave if true, then save the reminders even if they didn't change
+     * @return true if operations to update the database were added
+     */
+    public static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId,
+            ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
+            boolean forceSave) {
+        // If the reminders have not changed, then don't update the database
+        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
+            return false;
+        }
+
+        // Delete all the existing reminders for this event
+        String where = Reminders.EVENT_ID + "=?";
+        String[] args = new String[] {Long.toString(eventId)};
+        ContentProviderOperation.Builder b = ContentProviderOperation
+                .newDelete(Reminders.CONTENT_URI);
+        b.withSelection(where, args);
+        ops.add(b.build());
+
+        ContentValues values = new ContentValues();
+        int len = reminderMinutes.size();
+
+        // Insert the new reminders, if any
+        for (int i = 0; i < len; i++) {
+            int minutes = reminderMinutes.get(i);
+
+            values.clear();
+            values.put(Reminders.MINUTES, minutes);
+            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+            values.put(Reminders.EVENT_ID, eventId);
+            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
+            ops.add(b.build());
+        }
+        return true;
+    }
+
+    /**
+     * Saves the reminders, if they changed. Returns true if operations to
+     * update the database were added. Uses a reference id since an id isn't
+     * created until the row is added.
+     *
+     * @param ops the array of ContentProviderOperations
+     * @param eventId the id of the event whose reminders are being updated
+     * @param reminderMinutes the array of reminders set by the user
+     * @param originalMinutes the original array of reminders
+     * @param forceSave if true, then save the reminders even if they didn't change
+     * @return true if operations to update the database were added
+     */
+    public boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
+            int eventIdIndex, ArrayList<Integer> reminderMinutes,
+            ArrayList<Integer> originalMinutes, boolean forceSave) {
+        // If the reminders have not changed, then don't update the database
+        if (reminderMinutes.equals(originalMinutes) && !forceSave) {
+            return false;
+        }
+
+        // Delete all the existing reminders for this event
+        ContentProviderOperation.Builder b = ContentProviderOperation
+                .newDelete(Reminders.CONTENT_URI);
+        b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
+        b.withSelectionBackReference(0, eventIdIndex);
+        ops.add(b.build());
+
+        ContentValues values = new ContentValues();
+        int len = reminderMinutes.size();
+
+        // Insert the new reminders, if any
+        for (int i = 0; i < len; i++) {
+            int minutes = reminderMinutes.get(i);
+
+            values.clear();
+            values.put(Reminders.MINUTES, minutes);
+            values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+            b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
+            b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
+            ops.add(b.build());
+        }
+        return true;
+    }
+
+    // It's the first event in the series if the start time before being
+    // modified is the same as the original event's start time
+    static boolean isFirstEventInSeries(CalendarEventModel model,
+            CalendarEventModel originalModel) {
+        return model.mOriginalStart == originalModel.mStart;
+    }
+
+    // Adds an rRule and duration to a set of content values
+    void addRecurrenceRule(ContentValues values, CalendarEventModel model) {
+        String rrule = model.mRrule;
+
+        values.put(Events.RRULE, rrule);
+        long end = model.mEnd;
+        long start = model.mStart;
+        String duration = model.mDuration;
+
+        boolean isAllDay = model.mAllDay;
+        if (end > start) {
+            if (isAllDay) {
+                // if it's all day compute the duration in days
+                long days = (end - start + DateUtils.DAY_IN_MILLIS - 1)
+                        / DateUtils.DAY_IN_MILLIS;
+                duration = "P" + days + "D";
+            } else {
+                // otherwise compute the duration in seconds
+                long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
+                duration = "P" + seconds + "S";
+            }
+        } else if (TextUtils.isEmpty(duration)) {
+
+            // If no good duration info exists assume the default
+            if (isAllDay) {
+                duration = "P1D";
+            } else {
+                duration = "P3600S";
+            }
+        }
+        // recurring events should have a duration and dtend set to null
+        values.put(Events.DURATION, duration);
+        values.put(Events.DTEND, (Long) null);
+    }
+
+    /**
+     * Uses the recurrence selection and the model data to build an rrule and
+     * write it to the model.
+     *
+     * @param selection the type of rrule
+     * @param model The event to update
+     * @param weekStart the week start day, specified as java.util.Calendar
+     * constants
+     */
+    static void updateRecurrenceRule(int selection, CalendarEventModel model,
+            int weekStart) {
+        // Make sure we don't have any leftover data from the previous setting
+        EventRecurrence eventRecurrence = new EventRecurrence();
+
+        if (selection == DOES_NOT_REPEAT) {
+            model.mRrule = null;
+            return;
+        } else if (selection == REPEATS_CUSTOM) {
+            // Keep custom recurrence as before.
+            return;
+        } else if (selection == REPEATS_DAILY) {
+            eventRecurrence.freq = EventRecurrence.DAILY;
+        } else if (selection == REPEATS_EVERY_WEEKDAY) {
+            eventRecurrence.freq = EventRecurrence.WEEKLY;
+            int dayCount = 5;
+            int[] byday = new int[dayCount];
+            int[] bydayNum = new int[dayCount];
+
+            byday[0] = EventRecurrence.MO;
+            byday[1] = EventRecurrence.TU;
+            byday[2] = EventRecurrence.WE;
+            byday[3] = EventRecurrence.TH;
+            byday[4] = EventRecurrence.FR;
+            for (int day = 0; day < dayCount; day++) {
+                bydayNum[day] = 0;
+            }
+
+            eventRecurrence.byday = byday;
+            eventRecurrence.bydayNum = bydayNum;
+            eventRecurrence.bydayCount = dayCount;
+        } else if (selection == REPEATS_WEEKLY_ON_DAY) {
+            eventRecurrence.freq = EventRecurrence.WEEKLY;
+            int[] days = new int[1];
+            int dayCount = 1;
+            int[] dayNum = new int[dayCount];
+            Time startTime = new Time(model.mTimezone);
+            startTime.set(model.mStart);
+
+            days[0] = EventRecurrence.timeDay2Day(startTime.weekDay);
+            // not sure why this needs to be zero, but set it for now.
+            dayNum[0] = 0;
+
+            eventRecurrence.byday = days;
+            eventRecurrence.bydayNum = dayNum;
+            eventRecurrence.bydayCount = dayCount;
+        } else if (selection == REPEATS_MONTHLY_ON_DAY) {
+            eventRecurrence.freq = EventRecurrence.MONTHLY;
+            eventRecurrence.bydayCount = 0;
+            eventRecurrence.bymonthdayCount = 1;
+            int[] bymonthday = new int[1];
+            Time startTime = new Time(model.mTimezone);
+            startTime.set(model.mStart);
+            bymonthday[0] = startTime.monthDay;
+            eventRecurrence.bymonthday = bymonthday;
+        } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) {
+            eventRecurrence.freq = EventRecurrence.MONTHLY;
+            eventRecurrence.bydayCount = 1;
+            eventRecurrence.bymonthdayCount = 0;
+
+            int[] byday = new int[1];
+            int[] bydayNum = new int[1];
+            Time startTime = new Time(model.mTimezone);
+            startTime.set(model.mStart);
+            // Compute the week number (for example, the "2nd" Monday)
+            int dayCount = 1 + ((startTime.monthDay - 1) / 7);
+            if (dayCount == 5) {
+                dayCount = -1;
+            }
+            bydayNum[0] = dayCount;
+            byday[0] = EventRecurrence.timeDay2Day(startTime.weekDay);
+            eventRecurrence.byday = byday;
+            eventRecurrence.bydayNum = bydayNum;
+        } else if (selection == REPEATS_YEARLY) {
+            eventRecurrence.freq = EventRecurrence.YEARLY;
+        }
+
+        // Set the week start day.
+        eventRecurrence.wkst = EventRecurrence.calendarDay2Day(weekStart);
+        model.mRrule = eventRecurrence.toString();
+    }
+
+    /**
+     * Uses an event cursor to fill in the given model This method assumes the
+     * cursor used {@link #EVENT_PROJECTION} as it's query projection. It uses
+     * the cursor to fill in the given model with all the information available.
+     *
+     * @param model The model to fill in
+     * @param cursor An event cursor that used {@link #EVENT_PROJECTION} for the query
+     */
+    public static void setModelFromCursor(CalendarEventModel model, Cursor cursor) {
+        if (model == null || cursor == null || cursor.getCount() != 1) {
+            Log.wtf(TAG, "Attempted to build non-existent model or from an incorrect query.");
+            return;
+        }
+
+        model.clear();
+        cursor.moveToFirst();
+
+        model.mId = cursor.getInt(EVENT_INDEX_ID);
+        model.mTitle = cursor.getString(EVENT_INDEX_TITLE);
+        model.mDescription = cursor.getString(EVENT_INDEX_DESCRIPTION);
+        model.mLocation = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
+        model.mAllDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+        model.mHasAlarm = cursor.getInt(EVENT_INDEX_HAS_ALARM) != 0;
+        model.mCalendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID);
+        model.mStart = cursor.getLong(EVENT_INDEX_DTSTART);
+        model.mTimezone = cursor.getString(EVENT_INDEX_TIMEZONE);
+        String rRule = cursor.getString(EVENT_INDEX_RRULE);
+        model.mRrule = rRule;
+        model.mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
+        model.mTransparency = cursor.getInt(EVENT_INDEX_TRANSPARENCY) != 0;
+        int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
+        model.mOwnerAccount = cursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
+        model.mHasAttendeeData = cursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
+        model.mOriginalEvent = cursor.getString(EVENT_INDEX_ORIGINAL_EVENT);
+
+        if (visibility > 0) {
+            // For now the array contains the values 0, 2, and 3. We subtract
+            // one to make it easier to handle in code as 0,1,2.
+            // Default (0), Private (1), Public (2)
+            visibility--;
+        }
+        model.mVisibility = visibility;
+
+        boolean hasRRule = !TextUtils.isEmpty(rRule);
+
+        // We expect only one of these, so ignore the other
+        if (hasRRule) {
+            model.mDuration = cursor.getString(EVENT_INDEX_DURATION);
+        } else {
+            model.mEnd = cursor.getLong(EVENT_INDEX_DTEND);
+        }
+    }
+
+    /**
+     * Goes through an event model and fills in content values for saving This
+     * method will perform the initial collection of values from the model and
+     * put them into a set of ContentValues. It performs some basic work such as
+     * fixing the time on allDay events and choosing whether to use an rrule or
+     * dtend.
+     *
+     * @param model The complete model of the event you want to save
+     * @return values
+     */
+    ContentValues getContentValuesFromModel(CalendarEventModel model) {
+        String title = model.mTitle;
+        boolean isAllDay = model.mAllDay;
+        String location = model.mLocation.trim();
+        String description = model.mDescription.trim();
+        String rrule = model.mRrule;
+        String timezone = model.mTimezone;
+        if (timezone == null) {
+            timezone = TimeZone.getDefault().getID();
+        }
+        Time startTime = new Time(timezone);
+        Time endTime = new Time(timezone);
+
+        startTime.set(model.mStart);
+        endTime.set(model.mEnd);
+
+        ContentValues values = new ContentValues();
+
+        long startMillis;
+        long endMillis;
+        long calendarId = model.mCalendarId;
+        if (isAllDay) {
+            // Reset start and end time, ensure at least 1 day duration, and set
+            // the timezone to UTC, as required for all-day events.
+            timezone = Time.TIMEZONE_UTC;
+            startTime.hour = 0;
+            startTime.minute = 0;
+            startTime.second = 0;
+            startTime.timezone = timezone;
+            startMillis = startTime.normalize(true);
+
+            endTime.hour = 0;
+            endTime.minute = 0;
+            endTime.second = 0;
+            if (endTime.monthDay == startTime.monthDay) {
+                endTime.monthDay++;
+            }
+            endTime.timezone = timezone;
+            endMillis = endTime.normalize(true);
+        } else {
+            startMillis = startTime.toMillis(true);
+            endMillis = endTime.toMillis(true);
+        }
+
+        values.put(Events.CALENDAR_ID, calendarId);
+        values.put(Events.EVENT_TIMEZONE, timezone);
+        values.put(Events.TITLE, title);
+        values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
+        values.put(Events.DTSTART, startMillis);
+        values.put(Events.RRULE, rrule);
+        if (rrule != null) {
+            addRecurrenceRule(values, model);
+        } else {
+            values.put(Events.DURATION, (String) null);
+            values.put(Events.DTEND, endMillis);
+        }
+        values.put(Events.DESCRIPTION, description);
+        values.put(Events.EVENT_LOCATION, location);
+        values.put(Events.TRANSPARENCY, model.mTransparency ? 1 : 0);
+        values.put(Events.HAS_ATTENDEE_DATA, model.mHasAttendeeData ? 1 : 0);
+
+        int visibility = model.mVisibility;
+        if (visibility > 0) {
+            // For now the array contains the values 0, 2, and 3. We add one to match.
+            // Default (0), Private (2), Public (3)
+            visibility++;
+        }
+        values.put(Events.VISIBILITY, visibility);
+
+        return values;
+    }
+
+    /**
+     * Takes an e-mail address and returns the domain (everything after the last @)
+     */
+    public static String extractDomain(String email) {
+        int separator = email.lastIndexOf('@');
+        if (separator != -1 && ++separator < email.length()) {
+            return email.substring(separator);
+        }
+        return null;
+    }
+
+    public interface EditDoneRunnable extends Runnable {
+        public void setDoneCode(int code);
+    }
+}
diff --git a/src/com/android/calendar/event/EditEventView.java b/src/com/android/calendar/event/EditEventView.java
new file mode 100644
index 0000000..4dd51c1
--- /dev/null
+++ b/src/com/android/calendar/event/EditEventView.java
@@ -0,0 +1,1191 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.CalendarEventModel;
+import com.android.calendar.CalendarEventModel.Attendee;
+import com.android.calendar.EmailAddressAdapter;
+import com.android.calendar.GeneralPreferences;
+import com.android.calendar.R;
+import com.android.calendar.TimezoneAdapter;
+import com.android.calendar.TimezoneAdapter.TimezoneRow;
+import com.android.calendar.Utils;
+import com.android.calendar.event.EditEventHelper.EditDoneRunnable;
+import com.android.common.Rfc822InputFilter;
+import com.android.common.Rfc822Validator;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DatePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.ProgressDialog;
+import android.app.TimePickerDialog;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.pim.EventRecurrence;
+import android.provider.Calendar.Calendars;
+import android.provider.Settings;
+import android.text.InputFilter;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.util.Rfc822Tokenizer;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.DatePicker;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.MultiAutoCompleteTextView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.ScrollView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.TimeZone;
+
+public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener,
+        DialogInterface.OnClickListener {
+
+    private static final String TAG = "EditEvent";
+
+    private static final int REMINDER_FLING_VELOCITY = 2000;
+
+    private LayoutInflater mLayoutInflater;
+
+    TextView mLoadingMessage;
+    ScrollView mScrollView;
+    Button mStartDateButton;
+    Button mEndDateButton;
+    Button mStartTimeButton;
+    Button mEndTimeButton;
+    Button mSaveButton;
+    Button mDeleteButton;
+    Button mDiscardButton;
+    Button mTimezoneButton;
+    CheckBox mAllDayCheckBox;
+    Spinner mCalendarsSpinner;
+    Spinner mRepeatsSpinner;
+    Spinner mTransparencySpinner;
+    Spinner mVisibilitySpinner;
+    Spinner mResponseSpinner;
+    TextView mTitleTextView;
+    TextView mLocationTextView;
+    TextView mDescriptionTextView;
+    TextView mTimezoneTextView;
+    TextView mTimezoneFooterView;
+    View mRemindersSeparator;
+    LinearLayout mRemindersContainer;
+    MultiAutoCompleteTextView mAttendeesList;
+    ImageButton mAddAttendeesButton;
+    ListView mGuestList;
+    AttendeesAdapter mAttendeesAdapter;
+    AddAttendeeClickListener mAddAttendeesListener;
+
+    private ProgressDialog mLoadingCalendarsDialog;
+    private AlertDialog mNoCalendarsDialog;
+    private AlertDialog mTimezoneDialog;
+    private Activity mActivity;
+    private EditDoneRunnable mDone;
+    private View mView;
+    private CalendarEventModel mModel;
+    private Cursor mCalendarsCursor;
+    private EmailAddressAdapter mAddressAdapter;
+    private Rfc822Validator mEmailValidator;
+    private TimezoneAdapter mTimezoneAdapter;
+
+    private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer>(0);
+    private ArrayList<Integer> mReminderValues;
+    private ArrayList<String> mReminderLabels;
+    private int mDefaultReminderMinutes;
+
+    private boolean mSaveAfterQueryComplete = false;
+    private boolean mCalendarsCursorSet = false;
+
+    private Time mStartTime;
+    private Time mEndTime;
+    private String mTimezone;
+    private int mModification = EditEventHelper.MODIFY_UNINITIALIZED;
+
+    private EventRecurrence mEventRecurrence = new EventRecurrence();
+
+    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
+
+    /* This class is used to update the time buttons. */
+    private class TimeListener implements OnTimeSetListener {
+        private View mView;
+
+        public TimeListener(View view) {
+            mView = view;
+        }
+
+        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+            // Cache the member variables locally to avoid inner class overhead.
+            Time startTime = mStartTime;
+            Time endTime = mEndTime;
+
+            // Cache the start and end millis so that we limit the number
+            // of calls to normalize() and toMillis(), which are fairly
+            // expensive.
+            long startMillis;
+            long endMillis;
+            if (mView == mStartTimeButton) {
+                // The start time was changed.
+                int hourDuration = endTime.hour - startTime.hour;
+                int minuteDuration = endTime.minute - startTime.minute;
+
+                startTime.hour = hourOfDay;
+                startTime.minute = minute;
+                startMillis = startTime.normalize(true);
+
+                // Also update the end time to keep the duration constant.
+                endTime.hour = hourOfDay + hourDuration;
+                endTime.minute = minute + minuteDuration;
+            } else {
+                // The end time was changed.
+                startMillis = startTime.toMillis(true);
+                endTime.hour = hourOfDay;
+                endTime.minute = minute;
+
+                // Move to the start time if the end time is before the start
+                // time.
+                if (endTime.before(startTime)) {
+                    endTime.monthDay = startTime.monthDay + 1;
+                }
+            }
+
+            endMillis = endTime.normalize(true);
+
+            setDate(mEndDateButton, endMillis);
+            setTime(mStartTimeButton, startMillis);
+            setTime(mEndTimeButton, endMillis);
+        }
+    }
+
+    private class AddAttendeeClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View v) {
+            mAttendeesList.performValidation();
+            mAttendeesAdapter.addAttendees(mAttendeesList.getText().toString());
+            mAttendeesList.setText("");
+        }
+    }
+
+    private class TimeClickListener implements View.OnClickListener {
+        private Time mTime;
+
+        public TimeClickListener(Time time) {
+            mTime = time;
+        }
+
+        public void onClick(View v) {
+            new TimePickerDialog(mActivity, new TimeListener(v), mTime.hour, mTime.minute,
+                    DateFormat.is24HourFormat(mActivity)).show();
+        }
+    }
+
+    private class DateListener implements OnDateSetListener {
+        View mView;
+
+        public DateListener(View view) {
+            mView = view;
+        }
+
+        public void onDateSet(DatePicker view, int year, int month, int monthDay) {
+            // Cache the member variables locally to avoid inner class overhead.
+            Time startTime = mStartTime;
+            Time endTime = mEndTime;
+
+            // Cache the start and end millis so that we limit the number
+            // of calls to normalize() and toMillis(), which are fairly
+            // expensive.
+            long startMillis;
+            long endMillis;
+            if (mView == mStartDateButton) {
+                // The start date was changed.
+                int yearDuration = endTime.year - startTime.year;
+                int monthDuration = endTime.month - startTime.month;
+                int monthDayDuration = endTime.monthDay - startTime.monthDay;
+
+                startTime.year = year;
+                startTime.month = month;
+                startTime.monthDay = monthDay;
+                startMillis = startTime.normalize(true);
+
+                // Also update the end date to keep the duration constant.
+                endTime.year = year + yearDuration;
+                endTime.month = month + monthDuration;
+                endTime.monthDay = monthDay + monthDayDuration;
+                endMillis = endTime.normalize(true);
+
+                // If the start date has changed then update the repeats.
+                populateRepeats();
+            } else {
+                // The end date was changed.
+                startMillis = startTime.toMillis(true);
+                endTime.year = year;
+                endTime.month = month;
+                endTime.monthDay = monthDay;
+                endMillis = endTime.normalize(true);
+
+                // Do not allow an event to have an end time before the start
+                // time.
+                if (endTime.before(startTime)) {
+                    endTime.set(startTime);
+                    endMillis = startMillis;
+                }
+            }
+
+            setDate(mStartDateButton, startMillis);
+            setDate(mEndDateButton, endMillis);
+            setTime(mEndTimeButton, endMillis); // In case end time had to be
+            // reset
+        }
+    }
+
+    // Fills in the date and time fields
+    private void populateWhen() {
+        long startMillis = mStartTime.toMillis(false /* use isDst */);
+        long endMillis = mEndTime.toMillis(false /* use isDst */);
+        setDate(mStartDateButton, startMillis);
+        setDate(mEndDateButton, endMillis);
+
+        setTime(mStartTimeButton, startMillis);
+        setTime(mEndTimeButton, endMillis);
+
+        mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
+        mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
+
+        mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
+        mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
+    }
+
+    private void populateTimezone() {
+        mTimezoneButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showTimezoneDialog();
+            }
+        });
+        setTimezone(mTimezoneAdapter.getRowById(mTimezone));
+    }
+
+    private void showTimezoneDialog() {
+        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
+        AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
+        builder.setTitle(R.string.timezone_label);
+        builder.setSingleChoiceItems(
+                mTimezoneAdapter, mTimezoneAdapter.getRowById(mTimezone), this);
+        mTimezoneDialog = builder.create();
+        mTimezoneFooterView.setText(mActivity.getString(R.string.edit_event_show_all) + " >");
+        mTimezoneFooterView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mTimezoneDialog.getListView().removeFooterView(mTimezoneFooterView);
+                mTimezoneAdapter.showAllTimezones();
+                final int row = mTimezoneAdapter.getRowById(mTimezone);
+                // we need to post the selection changes to have them have
+                // any effect
+                mTimezoneDialog.getListView().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTimezoneDialog.getListView().setItemChecked(row, true);
+                        mTimezoneDialog.getListView().setSelection(row);
+                    }
+                });
+            }
+        });
+        mTimezoneDialog.getListView().addFooterView(mTimezoneFooterView);
+        mTimezoneDialog.show();
+    }
+
+    private void populateRepeats() {
+        Time time = mStartTime;
+        Resources r = mActivity.getResources();
+        int resource = android.R.layout.simple_spinner_item;
+
+        String[] days = new String[] {
+                DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
+                DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM), };
+        String[] ordinals = r.getStringArray(R.array.ordinal_labels);
+
+        // Only display "Custom" in the spinner if the device does not support
+        // the recurrence functionality of the event. Only display every weekday
+        // if the event starts on a weekday.
+        boolean isCustomRecurrence = isCustomRecurrence();
+        boolean isWeekdayEvent = isWeekdayEvent();
+
+        ArrayList<String> repeatArray = new ArrayList<String>(0);
+        ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
+
+        repeatArray.add(r.getString(R.string.does_not_repeat));
+        recurrenceIndexes.add(EditEventHelper.DOES_NOT_REPEAT);
+
+        repeatArray.add(r.getString(R.string.daily));
+        recurrenceIndexes.add(EditEventHelper.REPEATS_DAILY);
+
+        if (isWeekdayEvent) {
+            repeatArray.add(r.getString(R.string.every_weekday));
+            recurrenceIndexes.add(EditEventHelper.REPEATS_EVERY_WEEKDAY);
+        }
+
+        String format = r.getString(R.string.weekly);
+        repeatArray.add(String.format(format, time.format("%A")));
+        recurrenceIndexes.add(EditEventHelper.REPEATS_WEEKLY_ON_DAY);
+
+        // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance
+        // of the given day.
+        int dayNumber = (time.monthDay - 1) / 7;
+        format = r.getString(R.string.monthly_on_day_count);
+        repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
+        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
+
+        format = r.getString(R.string.monthly_on_day);
+        repeatArray.add(String.format(format, time.monthDay));
+        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY);
+
+        long when = time.toMillis(false);
+        format = r.getString(R.string.yearly);
+        int flags = 0;
+        if (DateFormat.is24HourFormat(mActivity)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        repeatArray.add(String.format(format, DateUtils.formatDateTime(mActivity, when, flags)));
+        recurrenceIndexes.add(EditEventHelper.REPEATS_YEARLY);
+
+        if (isCustomRecurrence) {
+            repeatArray.add(r.getString(R.string.custom));
+            recurrenceIndexes.add(EditEventHelper.REPEATS_CUSTOM);
+        }
+        mRecurrenceIndexes = recurrenceIndexes;
+
+        int position = recurrenceIndexes.indexOf(EditEventHelper.DOES_NOT_REPEAT);
+        if (mModel.mRrule != null) {
+            if (isCustomRecurrence) {
+                position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_CUSTOM);
+            } else {
+                switch (mEventRecurrence.freq) {
+                    case EventRecurrence.DAILY:
+                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_DAILY);
+                        break;
+                    case EventRecurrence.WEEKLY:
+                        if (mEventRecurrence.repeatsOnEveryWeekDay()) {
+                            position = recurrenceIndexes.indexOf(
+                                    EditEventHelper.REPEATS_EVERY_WEEKDAY);
+                        } else {
+                            position = recurrenceIndexes.indexOf(
+                                    EditEventHelper.REPEATS_WEEKLY_ON_DAY);
+                        }
+                        break;
+                    case EventRecurrence.MONTHLY:
+                        if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
+                            position = recurrenceIndexes.indexOf(
+                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
+                        } else {
+                            position = recurrenceIndexes.indexOf(
+                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY);
+                        }
+                        break;
+                    case EventRecurrence.YEARLY:
+                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_YEARLY);
+                        break;
+                }
+            }
+        }
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, resource, repeatArray);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mRepeatsSpinner.setAdapter(adapter);
+        mRepeatsSpinner.setSelection(position);
+
+        // Don't allow the user to make exceptions recurring events.
+        if (mModel.mOriginalEvent != null) {
+            mRepeatsSpinner.setEnabled(false);
+        }
+    }
+
+    private boolean isCustomRecurrence() {
+
+        if (mEventRecurrence.until != null || mEventRecurrence.interval != 0) {
+            return true;
+        }
+
+        if (mEventRecurrence.freq == 0) {
+            return false;
+        }
+
+        switch (mEventRecurrence.freq) {
+            case EventRecurrence.DAILY:
+                return false;
+            case EventRecurrence.WEEKLY:
+                if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
+                    return false;
+                } else if (mEventRecurrence.bydayCount == 1) {
+                    return false;
+                }
+                break;
+            case EventRecurrence.MONTHLY:
+                if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
+                    return false;
+                } else if (mEventRecurrence.bydayCount == 0
+                        && mEventRecurrence.bymonthdayCount == 1) {
+                    return false;
+                }
+                break;
+            case EventRecurrence.YEARLY:
+                return false;
+        }
+
+        return true;
+    }
+
+    private boolean isWeekdayEvent() {
+        if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
+            return true;
+        }
+        return false;
+    }
+
+    private class DateClickListener implements View.OnClickListener {
+        private Time mTime;
+
+        public DateClickListener(Time time) {
+            mTime = time;
+        }
+
+        public void onClick(View v) {
+            new DatePickerDialog(
+                    mActivity, new DateListener(v), mTime.year, mTime.month, mTime.monthDay).show();
+        }
+    }
+
+    static private class CalendarsAdapter extends ResourceCursorAdapter {
+        public CalendarsAdapter(Context context, Cursor c) {
+            super(context, R.layout.calendars_item, c);
+            setDropDownViewResource(R.layout.calendars_dropdown_item);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            View colorBar = view.findViewById(R.id.color);
+            int colorColumn = cursor.getColumnIndexOrThrow(Calendars.COLOR);
+            int nameColumn = cursor.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
+            int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
+            if (colorBar != null) {
+                colorBar.setBackgroundDrawable(Utils.getColorChip(cursor.getInt(colorColumn)));
+            }
+
+            TextView name = (TextView) view.findViewById(R.id.calendar_name);
+            if (name != null) {
+                String displayName = cursor.getString(nameColumn);
+                name.setText(displayName);
+                name.setTextColor(0xFF000000);
+
+                TextView accountName = (TextView) view.findViewById(R.id.account_name);
+                if (accountName != null) {
+                    Resources res = context.getResources();
+                    accountName.setText(cursor.getString(ownerColumn));
+                    accountName.setVisibility(TextView.VISIBLE);
+                    accountName.setTextColor(res.getColor(R.color.calendar_owner_text_color));
+                }
+            }
+        }
+    }
+
+    /**
+     * Does prep steps for saving a calendar event.
+     *
+     * This triggers a parse of the attendees list and checks if the event is
+     * ready to be saved. An event is ready to be saved so long as it has a
+     * calendar it can be associated with, either because it's an existing event
+     * or we've finished querying
+     *
+     * @return false if no calendar had been loaded yet, true otherwise
+     */
+    public boolean prepareForSave() {
+        if (mCalendarsCursor == null && mModel.mUri == null) {
+            return false;
+        }
+        mAddAttendeesListener.onClick(null);
+        fillModelFromUI();
+        return true;
+    }
+
+    // This is called if the user clicks on one of the buttons: "Save",
+    // "Discard", or "Delete". This is also called if the user clicks
+    // on the "remove reminder" button.
+    public void onClick(View v) {
+        if (v == mSaveButton) {
+            // If we're creating a new event but haven't gotten any calendars
+            // yet let the user know we're waiting for calendars to finish
+            // loading. The save button isn't enabled until we have a non-null
+            // mModel.
+            mAddAttendeesListener.onClick(v);
+            if (mCalendarsCursor == null && mModel.mUri == null) {
+                if (mLoadingCalendarsDialog == null) {
+                    // Create the progress dialog
+                    mLoadingCalendarsDialog = ProgressDialog.show(mActivity,
+                            mActivity.getText(R.string.loading_calendars_title),
+                            mActivity.getText(R.string.loading_calendars_message), true, true,
+                            this);
+                    mSaveAfterQueryComplete = true;
+                }
+            } else if (fillModelFromUI()) {
+                mDone.setDoneCode(Utils.DONE_SAVE | Utils.DONE_EXIT);
+                mDone.run();
+            } else {
+                mDone.setDoneCode(Utils.DONE_REVERT);
+                mDone.run();
+            }
+            return;
+        }
+
+        if (v == mDeleteButton) {
+            mDone.setDoneCode(Utils.DONE_DELETE | Utils.DONE_EXIT);
+            mDone.run();
+            return;
+        }
+
+        if (v == mDiscardButton) {
+            mDone.setDoneCode(Utils.DONE_REVERT);
+            mDone.run();
+            return;
+        }
+
+        // This must be a click on one of the "remove reminder" buttons
+        LinearLayout reminderItem = (LinearLayout) v.getParent();
+        LinearLayout parent = (LinearLayout) reminderItem.getParent();
+        parent.removeView(reminderItem);
+        mReminderItems.remove(reminderItem);
+        updateRemindersVisibility(mReminderItems.size());
+    }
+
+    // This is called if the user cancels the "No calendars" dialog.
+    // The "No calendars" dialog is shown if there are no syncable calendars.
+    public void onCancel(DialogInterface dialog) {
+        if (dialog == mLoadingCalendarsDialog) {
+            mLoadingCalendarsDialog = null;
+            mSaveAfterQueryComplete = false;
+        } else if (dialog == mNoCalendarsDialog) {
+            mDone.setDoneCode(Utils.DONE_REVERT);
+            mDone.run();
+            return;
+        }
+    }
+
+    // This is called if the user clicks on a dialog button.
+    public void onClick(DialogInterface dialog, int which) {
+        if (dialog == mNoCalendarsDialog) {
+            mDone.setDoneCode(Utils.DONE_REVERT);
+            mDone.run();
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
+                final String[] array = {"com.android.calendar"};
+                nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
+                mActivity.startActivity(nextIntent);
+            }
+        } else if (dialog == mTimezoneDialog) {
+            if (which >= 0 && which < mTimezoneAdapter.getCount()) {
+                setTimezone(which);
+                dialog.dismiss();
+            }
+        }
+    }
+
+    // Goes through the UI elements and updates the model as necessary
+    public boolean fillModelFromUI() {
+        if (mModel == null) {
+            return false;
+        }
+        mModel.mReminderMinutes = EventViewUtils.reminderItemsToMinutes(
+                mReminderItems, mReminderValues);
+        mModel.mHasAlarm = mReminderItems.size() > 0;
+        mModel.mTitle = mTitleTextView.getText().toString().trim();
+        mModel.mAllDay = mAllDayCheckBox.isChecked();
+        mModel.mLocation = mLocationTextView.getText().toString().trim();
+        mModel.mDescription = mDescriptionTextView.getText().toString().trim();
+        int position = mResponseSpinner.getSelectedItemPosition();
+        if (position > 0) {
+            mModel.mSelfAttendeeStatus = EditEventHelper.ATTENDEE_VALUES[position];
+        }
+
+        if (mGuestList != null) {
+            AttendeesAdapter adapter = mAttendeesAdapter;
+            if (adapter != null && !adapter.isEmpty()) {
+                int size = adapter.getCount();
+                mModel.mAttendeesList.clear();
+                for (int i = 0; i < size; i++) {
+                    Attendee attendee = adapter.getItem(i);
+                    if (attendee == null || adapter.isRemoved(i)) {
+                        continue;
+                    }
+                    mModel.addAttendee(attendee);
+                }
+            }
+        }
+
+        // If this was a new event we need to fill in the Calendar information
+        if (mModel.mUri == null) {
+            mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId();
+            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
+            if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
+                String defaultCalendar = mCalendarsCursor.getString(
+                        EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
+                Utils.setSharedPreference(
+                        mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar);
+                mModel.mOwnerAccount = defaultCalendar;
+                mModel.mOrganizer = defaultCalendar;
+                mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID);
+            }
+        }
+
+        if (mModel.mAllDay) {
+            // Reset start and end time, increment the monthDay by 1, and set
+            // the timezone to UTC, as required for all-day events.
+            mTimezone = Time.TIMEZONE_UTC;
+            mStartTime.hour = 0;
+            mStartTime.minute = 0;
+            mStartTime.second = 0;
+            mStartTime.timezone = mTimezone;
+            mModel.mStart = mStartTime.normalize(true);
+
+            // Round up to the next day
+            if (mEndTime.hour > 0 || mEndTime.minute > 0 || mEndTime.second > 0
+                    || mEndTime.monthDay == mStartTime.monthDay) {
+                mEndTime.monthDay++;
+            }
+            mEndTime.hour = 0;
+            mEndTime.minute = 0;
+            mEndTime.second = 0;
+            mEndTime.timezone = mTimezone;
+            mModel.mEnd = mEndTime.normalize(true);
+        } else {
+            mStartTime.timezone = mTimezone;
+            mEndTime.timezone = mTimezone;
+            mModel.mStart = mStartTime.toMillis(true);
+            mModel.mEnd = mEndTime.toMillis(true);
+        }
+        mModel.mTimezone = mTimezone;
+        mModel.mVisibility = mVisibilitySpinner.getSelectedItemPosition();
+        mModel.mTransparency = mTransparencySpinner.getSelectedItemPosition() != 0;
+
+        int selection;
+        // If we're making an exception we don't want it to be a repeating
+        // event.
+        if (mModification == EditEventHelper.MODIFY_SELECTED) {
+            selection = EditEventHelper.DOES_NOT_REPEAT;
+        } else {
+            position = mRepeatsSpinner.getSelectedItemPosition();
+            selection = mRecurrenceIndexes.get(position);
+        }
+
+        EditEventHelper.updateRecurrenceRule(
+                selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1);
+
+        // Save the timezone so we can display it as a standard option next time
+        if (!mModel.mAllDay) {
+            mTimezoneAdapter.saveRecentTimezone(mTimezone);
+        }
+        return true;
+    }
+
+    public EditEventView(Activity activity, View view, EditDoneRunnable done) {
+
+        mActivity = activity;
+        mView = view;
+        mDone = done;
+
+        // cache top level view elements
+        mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
+        mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
+
+        mLayoutInflater = activity.getLayoutInflater();
+
+        // cache all the widgets
+        mTitleTextView = (TextView) view.findViewById(R.id.title);
+        mLocationTextView = (TextView) view.findViewById(R.id.location);
+        mDescriptionTextView = (TextView) view.findViewById(R.id.description);
+        mTimezoneTextView = (TextView) view.findViewById(R.id.timezone_label);
+        mTimezoneFooterView = (TextView) mLayoutInflater.inflate(R.layout.timezone_footer, null);
+        mStartDateButton = (Button) view.findViewById(R.id.start_date);
+        mEndDateButton = (Button) view.findViewById(R.id.end_date);
+        mStartTimeButton = (Button) view.findViewById(R.id.start_time);
+        mEndTimeButton = (Button) view.findViewById(R.id.end_time);
+        mTimezoneButton = (Button) view.findViewById(R.id.timezone);
+        mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day);
+        mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars);
+        mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats);
+        mTransparencySpinner = (Spinner) view.findViewById(R.id.availability);
+        mVisibilitySpinner = (Spinner) view.findViewById(R.id.visibility);
+        mResponseSpinner = (Spinner) view.findViewById(R.id.response_value);
+        mRemindersSeparator = view.findViewById(R.id.reminders_separator);
+        mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
+
+        mSaveButton = (Button) view.findViewById(R.id.save);
+        mDeleteButton = (Button) view.findViewById(R.id.delete);
+
+        mDiscardButton = (Button) view.findViewById(R.id.discard);
+        mDiscardButton.setOnClickListener(this);
+
+        mAddAttendeesButton = (ImageButton) view.findViewById(R.id.attendee_add);
+        mAddAttendeesListener = new AddAttendeeClickListener();
+        mAddAttendeesButton.setOnClickListener(mAddAttendeesListener);
+
+        mStartTime = new Time();
+        mEndTime = new Time();
+        mTimezone = TimeZone.getDefault().getID();
+        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
+
+        mGuestList = (ListView) view.findViewById(R.id.attendee_list);
+
+        // Display loading screen
+        setModel(null);
+    }
+
+    /**
+     * Fill in the view with the contents of the given event model. This allows
+     * an edit view to be initialized before the event has been loaded. Passing
+     * in null for the model will display a loading screen. A non-null model
+     * will fill in the view's fields with the data contained in the model.
+     *
+     * @param model The event model to pull the data from
+     */
+    public void setModel(CalendarEventModel model) {
+        mModel = model;
+
+        // Need to close the autocomplete adapter to prevent leaking cursors.
+        if (mAddressAdapter != null) {
+            mAddressAdapter.close();
+            mAddressAdapter = null;
+        }
+
+        if (model == null) {
+            // Display loading screen
+            mLoadingMessage.setVisibility(View.VISIBLE);
+            mScrollView.setVisibility(View.GONE);
+            mSaveButton.setEnabled(false);
+            mDeleteButton.setEnabled(false);
+            return;
+        }
+
+        long begin = model.mStart;
+        long end = model.mEnd;
+        mTimezone = model.mTimezone; // this will be UTC for all day events
+
+        // Set up the starting times
+        if (begin > 0) {
+            mStartTime.timezone = mTimezone;
+            mStartTime.set(begin);
+            mStartTime.normalize(true);
+        }
+        if (end > 0) {
+            mEndTime.timezone = mTimezone;
+            mEndTime.set(end);
+            mEndTime.normalize(true);
+        }
+        String rrule = model.mRrule;
+        if (rrule != null) {
+            mEventRecurrence.parse(rrule);
+        }
+
+        // If the user is allowed to change the attendees set up the view and
+        // validator
+        if (model.mHasAttendeeData) {
+            String domain = "gmail.com";
+            if (!TextUtils.isEmpty(model.mOwnerAccount)) {
+                String ownerDomain = EditEventHelper.extractDomain(model.mOwnerAccount);
+                if (!TextUtils.isEmpty(ownerDomain)) {
+                    domain = ownerDomain;
+                }
+            }
+            mAddressAdapter = new EmailAddressAdapter(mActivity);
+            mEmailValidator = new Rfc822Validator(domain);
+            mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees);
+        } else {
+            View attGroup = mView.findViewById(R.id.attendees_group);
+            attGroup.setVisibility(View.GONE);
+        }
+
+        mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    if (mEndTime.hour == 0 && mEndTime.minute == 0) {
+                        mEndTime.monthDay--;
+                        long endMillis = mEndTime.normalize(true);
+
+                        // Do not allow an event to have an end time before the
+                        // start time.
+                        if (mEndTime.before(mStartTime)) {
+                            mEndTime.set(mStartTime);
+                            endMillis = mEndTime.normalize(true);
+                        }
+                        setDate(mEndDateButton, endMillis);
+                        setTime(mEndTimeButton, endMillis);
+                    }
+
+                    mStartTimeButton.setVisibility(View.GONE);
+                    mEndTimeButton.setVisibility(View.GONE);
+                    mTimezoneButton.setVisibility(View.GONE);
+                    mTimezoneTextView.setVisibility(View.GONE);
+                } else {
+                    if (mEndTime.hour == 0 && mEndTime.minute == 0) {
+                        mEndTime.monthDay++;
+                        long endMillis = mEndTime.normalize(true);
+                        setDate(mEndDateButton, endMillis);
+                        setTime(mEndTimeButton, endMillis);
+                    }
+                    mStartTimeButton.setVisibility(View.VISIBLE);
+                    mEndTimeButton.setVisibility(View.VISIBLE);
+                    mTimezoneButton.setVisibility(View.VISIBLE);
+                    mTimezoneTextView.setVisibility(View.VISIBLE);
+                }
+            }
+        });
+
+        if (model.mAllDay) {
+            mAllDayCheckBox.setChecked(true);
+            // put things back in local time for all day events
+            mTimezone = TimeZone.getDefault().getID();
+            mStartTime.timezone = mTimezone;
+            mStartTime.normalize(true);
+            mEndTime.timezone = mTimezone;
+            mEndTime.normalize(true);
+        } else {
+            mAllDayCheckBox.setChecked(false);
+        }
+
+        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
+        if (mTimezoneDialog != null) {
+            mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter);
+        }
+
+        mSaveButton.setOnClickListener(this);
+        mDeleteButton.setOnClickListener(this);
+        mSaveButton.setEnabled(true);
+        mDeleteButton.setEnabled(true);
+
+        // Initialize the reminder values array.
+        Resources r = mActivity.getResources();
+        String[] strings = r.getStringArray(R.array.reminder_minutes_values);
+        int size = strings.length;
+        ArrayList<Integer> list = new ArrayList<Integer>(size);
+        for (int i = 0; i < size; i++) {
+            list.add(Integer.parseInt(strings[i]));
+        }
+        mReminderValues = list;
+        String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
+        mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
+
+        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
+        String durationString = prefs.getString(GeneralPreferences.KEY_DEFAULT_REMINDER, "0");
+        mDefaultReminderMinutes = Integer.parseInt(durationString);
+
+        int numReminders = 0;
+        if (model.mHasAlarm) {
+            ArrayList<Integer> minutes = model.mReminderMinutes;
+            numReminders = minutes.size();
+            for (Integer minute : minutes) {
+                EventViewUtils.addMinutesToList(
+                        mActivity, mReminderValues, mReminderLabels, minute);
+                EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
+                        mReminderValues, mReminderLabels, minute);
+            }
+        }
+        updateRemindersVisibility(numReminders);
+
+        // Setup the + Add Reminder Button
+        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
+            public void onClick(View v) {
+                addReminder();
+            }
+        };
+        ImageButton reminderRemoveButton = (ImageButton) mView.findViewById(R.id.reminder_add);
+        reminderRemoveButton.setOnClickListener(addReminderOnClickListener);
+
+        mTitleTextView.setText(model.mTitle);
+        mLocationTextView.setText(model.mLocation);
+        mDescriptionTextView.setText(model.mDescription);
+        mTransparencySpinner.setSelection(model.mTransparency ? 1 : 0);
+        mVisibilitySpinner.setSelection(model.mVisibility);
+        mResponseSpinner.setSelection(findResponseIndexFor(model.mSelfAttendeeStatus));
+
+        if (model.mUri != null) {
+            // This is an existing event so hide the calendar spinner
+            // since we can't change the calendar.
+            View calendarGroup = mView.findViewById(R.id.calendar_group);
+            calendarGroup.setVisibility(View.GONE);
+        } else {
+            mDeleteButton.setVisibility(View.GONE);
+        }
+
+        populateWhen();
+        populateTimezone();
+        populateRepeats();
+        updateAttendees(model.mAttendeesList);
+        mScrollView.setVisibility(View.VISIBLE);
+        mLoadingMessage.setVisibility(View.GONE);
+    }
+
+    private int findResponseIndexFor(int response) {
+        int size = EditEventHelper.ATTENDEE_VALUES.length;
+        for (int index = 0; index < size; index++) {
+            if (EditEventHelper.ATTENDEE_VALUES[index] == response) {
+                return index;
+            }
+        }
+        return 0;
+    }
+
+    public void setCalendarsCursor(Cursor cursor, boolean userVisible) {
+        // If there are no syncable calendars, then we cannot allow
+        // creating a new event.
+        mCalendarsCursor = cursor;
+        if (cursor == null || cursor.getCount() == 0) {
+            // Cancel the "loading calendars" dialog if it exists
+            if (mSaveAfterQueryComplete) {
+                mLoadingCalendarsDialog.cancel();
+            }
+            if (!userVisible) {
+                return;
+            }
+            // Create an error message for the user that, when clicked,
+            // will exit this activity without saving the event.
+            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
+            builder.setTitle(R.string.no_syncable_calendars)
+                    .setIcon(android.R.drawable.ic_dialog_alert)
+                    .setMessage(R.string.no_calendars_found)
+                    .setPositiveButton(R.string.add_account, this)
+                    .setNegativeButton(android.R.string.no, this)
+                    .setOnCancelListener(this);
+            mNoCalendarsDialog = builder.show();
+            return;
+        }
+
+        int defaultCalendarPosition = findDefaultCalendarPosition(cursor);
+
+        // populate the calendars spinner
+        CalendarsAdapter adapter = new CalendarsAdapter(mActivity, cursor);
+        mCalendarsSpinner.setAdapter(adapter);
+        mCalendarsSpinner.setSelection(defaultCalendarPosition);
+        mCalendarsCursorSet = true;
+
+        // Find user domain and set it to the validator.
+        // TODO: we may want to update this validator if the user actually picks
+        // a different calendar. maybe not. depends on what we want for the
+        // user experience. this may change when we add support for multiple
+        // accounts, anyway.
+        if (mModel != null && mModel.mHasAttendeeData
+                && cursor.moveToPosition(defaultCalendarPosition)) {
+            String ownEmail = cursor.getString(EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
+            if (ownEmail != null) {
+                String domain = EditEventHelper.extractDomain(ownEmail);
+                if (domain != null) {
+                    mEmailValidator = new Rfc822Validator(domain);
+                    mAttendeesList.setValidator(mEmailValidator);
+                }
+            }
+        }
+        if (mSaveAfterQueryComplete) {
+            mLoadingCalendarsDialog.cancel();
+            if (prepareForSave() && fillModelFromUI()) {
+                int exit = userVisible ? Utils.DONE_EXIT : 0;
+                mDone.setDoneCode(Utils.DONE_SAVE | exit);
+                mDone.run();
+            } else if (userVisible) {
+                mDone.setDoneCode(Utils.DONE_EXIT);
+                mDone.run();
+            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view");
+            }
+            return;
+        }
+    }
+
+    public void setModification(int modifyWhich) {
+        mModification = modifyWhich;
+        // If we are modifying all the events in a
+        // series then disable and ignore the date.
+        if (modifyWhich == Utils.MODIFY_ALL) {
+            mStartDateButton.setEnabled(false);
+            mEndDateButton.setEnabled(false);
+        } else if (modifyWhich == Utils.MODIFY_SELECTED) {
+            mRepeatsSpinner.setEnabled(false);
+        }
+    }
+
+    // Find the calendar position in the cursor that matches calendar in
+    // preference
+    private int findDefaultCalendarPosition(Cursor calendarsCursor) {
+        if (calendarsCursor.getCount() <= 0) {
+            return -1;
+        }
+
+        String defaultCalendar = Utils.getSharedPreference(
+                mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, null);
+
+        if (defaultCalendar == null) {
+            return 0;
+        }
+        int calendarsOwnerColumn = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
+        int position = 0;
+        calendarsCursor.moveToPosition(-1);
+        while (calendarsCursor.moveToNext()) {
+            if (defaultCalendar.equals(calendarsCursor.getString(calendarsOwnerColumn))) {
+                return position;
+            }
+            position++;
+        }
+        return 0;
+    }
+
+    public void updateAttendees(HashMap<String, Attendee> attendeesList) {
+        if (mAttendeesAdapter == null) {
+            mAttendeesAdapter = new AttendeesAdapter(mActivity, mEmailValidator);
+        }
+        if (attendeesList.size() > 0) {
+            mAttendeesAdapter.addAttendees(attendeesList);
+        }
+        mGuestList.setAdapter(mAttendeesAdapter);
+    }
+
+    private void updateRemindersVisibility(int numReminders) {
+        if (numReminders == 0) {
+            mRemindersSeparator.setVisibility(View.GONE);
+            mRemindersContainer.setVisibility(View.GONE);
+        } else {
+            mRemindersSeparator.setVisibility(View.VISIBLE);
+            mRemindersContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public void addReminder() {
+        // TODO: when adding a new reminder, make it different from the
+        // last one in the list (if any).
+        if (mDefaultReminderMinutes == 0) {
+            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
+                    mReminderValues, mReminderLabels, 10 /* minutes */);
+        } else {
+            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
+                    mReminderValues, mReminderLabels, mDefaultReminderMinutes);
+        }
+        updateRemindersVisibility(mReminderItems.size());
+        mScrollView.fling(REMINDER_FLING_VELOCITY);
+    }
+
+    public int getReminderCount() {
+        return mReminderItems.size();
+    }
+
+    // From com.google.android.gm.ComposeActivity
+    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) {
+        MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) mView.findViewById(res);
+        list.setAdapter(mAddressAdapter);
+        list.setTokenizer(new Rfc822Tokenizer());
+        list.setValidator(mEmailValidator);
+
+        // NOTE: assumes no other filters are set
+        list.setFilters(sRecipientFilters);
+
+        return list;
+    }
+
+    /**
+     * From com.google.android.gm.ComposeActivity Implements special address
+     * cleanup rules: The first space key entry following an "@" symbol that is
+     * followed by any combination of letters and symbols, including one+ dots
+     * and zero commas, should insert an extra comma (followed by the space).
+     */
+    private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
+
+    private void setDate(TextView view, long millis) {
+        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
+                | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH
+                | DateUtils.FORMAT_ABBREV_WEEKDAY;
+
+        // Unfortunately, DateUtils doesn't support a timezone other than the
+        // default timezone provided by the system, so we have this ugly hack
+        // here to trick it into formatting our time correctly. In order to
+        // prevent all sorts of craziness, we synchronize on the TimeZone class
+        // to prevent other threads from reading an incorrect timezone from
+        // calls to TimeZone#getDefault()
+        // TODO fix this if/when DateUtils allows for passing in a timezone
+        String dateString;
+        synchronized (TimeZone.class) {
+            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
+            dateString = DateUtils.formatDateTime(mActivity, millis, flags);
+            // setting the default back to null restores the correct behavior
+            TimeZone.setDefault(null);
+        }
+        view.setText(dateString);
+    }
+
+    private void setTime(TextView view, long millis) {
+        int flags = DateUtils.FORMAT_SHOW_TIME;
+        if (DateFormat.is24HourFormat(mActivity)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+
+        // Unfortunately, DateUtils doesn't support a timezone other than the
+        // default timezone provided by the system, so we have this ugly hack
+        // here to trick it into formatting our time correctly. In order to
+        // prevent all sorts of craziness, we synchronize on the TimeZone class
+        // to prevent other threads from reading an incorrect timezone from
+        // calls to TimeZone#getDefault()
+        // TODO fix this if/when DateUtils allows for passing in a timezone
+        String timeString;
+        synchronized (TimeZone.class) {
+            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
+            timeString = DateUtils.formatDateTime(mActivity, millis, flags);
+            TimeZone.setDefault(null);
+        }
+        view.setText(timeString);
+    }
+
+    private void setTimezone(int i) {
+        if (i < 0 || i >= mTimezoneAdapter.getCount()) {
+            return; // do nothing
+        }
+        TimezoneRow timezone = mTimezoneAdapter.getItem(i);
+        mTimezoneButton.setText(timezone.toString());
+        mTimezone = timezone.mId;
+        mStartTime.timezone = mTimezone;
+        mStartTime.normalize(true);
+        mEndTime.timezone = mTimezone;
+        mEndTime.normalize(true);
+        mTimezoneAdapter.setCurrentTimezone(mTimezone);
+    }
+}
diff --git a/src/com/android/calendar/event/EventViewUtils.java b/src/com/android/calendar/event/EventViewUtils.java
new file mode 100644
index 0000000..fe05576
--- /dev/null
+++ b/src/com/android/calendar/event/EventViewUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+
+import java.util.ArrayList;
+
+public class EventViewUtils {
+
+    private EventViewUtils() {
+    }
+
+    // Constructs a label given an arbitrary number of minutes. For example,
+    // if the given minutes is 63, then this returns the string "63 minutes".
+    // As another example, if the given minutes is 120, then this returns
+    // "2 hours".
+    public static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
+        Resources resources = context.getResources();
+        int value, resId;
+
+        if (minutes % 60 != 0) {
+            value = minutes;
+            if (abbrev) {
+                resId = R.plurals.Nmins;
+            } else {
+                resId = R.plurals.Nminutes;
+            }
+        } else if (minutes % (24 * 60) != 0) {
+            value = minutes / 60;
+            resId = R.plurals.Nhours;
+        } else {
+            value = minutes / (24 * 60);
+            resId = R.plurals.Ndays;
+        }
+
+        String format = resources.getQuantityString(resId, value);
+        return String.format(format, value);
+    }
+
+    /**
+     * Finds the index of the given "minutes" in the "values" list.
+     *
+     * @param values the list of minutes corresponding to the spinner choices
+     * @param minutes the minutes to search for in the values list
+     * @return the index of "minutes" in the "values" list
+     */
+    public static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) {
+        int index = values.indexOf(minutes);
+        if (index == -1) {
+            // This should never happen.
+            Log.e("Cal", "Cannot find minutes (" + minutes + ") in list");
+            return 0;
+        }
+        return index;
+    }
+
+    public static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
+            ArrayList<Integer> reminderValues) {
+        int len = reminderItems.size();
+        ArrayList<Integer> reminderMinutes = new ArrayList<Integer>(len);
+        for (int index = 0; index < len; index++) {
+            LinearLayout layout = reminderItems.get(index);
+            Spinner spinner = (Spinner) layout.findViewById(R.id.reminder_value);
+            int minutes = reminderValues.get(spinner.getSelectedItemPosition());
+            reminderMinutes.add(minutes);
+        }
+        return reminderMinutes;
+    }
+
+    // Checks our list of minute value-label pairs and adds any custom times
+    // this event
+    // might contain.
+    public static void addMinutesToList(Context context, ArrayList<Integer> values,
+            ArrayList<String> labels, int minutes) {
+        int index = values.indexOf(minutes);
+        if (index != -1) {
+            return;
+        }
+
+        // The requested "minutes" does not exist in the list, so insert it
+        // into the list.
+
+        String label = constructReminderLabel(context, minutes, false);
+        int len = values.size();
+        for (int i = 0; i < len; i++) {
+            if (minutes < values.get(i)) {
+                values.add(i, minutes);
+                labels.add(i, label);
+                return;
+            }
+        }
+
+        values.add(minutes);
+        labels.add(len, label);
+    }
+
+    // Adds a reminder to the displayed list of reminders.
+    // Returns true if successfully added reminder, false if no reminders can
+    // be added.
+    public static boolean addReminder(Activity activity, View view, View.OnClickListener listener,
+            ArrayList<LinearLayout> items, ArrayList<Integer> values, ArrayList<String> labels,
+            int minutes) {
+
+        if (items.size() >= EditEventHelper.MAX_REMINDERS) {
+            return false;
+        }
+
+        LayoutInflater inflater = activity.getLayoutInflater();
+        LinearLayout parent = (LinearLayout) view.findViewById(R.id.reminder_items_container);
+        LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item,
+                null);
+        parent.addView(reminderItem);
+
+        Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value);
+        Resources res = activity.getResources();
+        spinner.setPrompt(res.getString(R.string.reminders_label));
+        int resource = android.R.layout.simple_spinner_item;
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, resource, labels);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinner.setAdapter(adapter);
+
+        ImageButton reminderRemoveButton;
+        reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove);
+        reminderRemoveButton.setOnClickListener(listener);
+
+        int index = findMinutesInReminderList(values, minutes);
+        spinner.setSelection(index);
+
+        items.add(reminderItem);
+
+        return true;
+    }
+
+}
diff --git a/src/com/android/calendar/month/FullMonthView.java b/src/com/android/calendar/month/FullMonthView.java
new file mode 100644
index 0000000..4f97680
--- /dev/null
+++ b/src/com/android/calendar/month/FullMonthView.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2010 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.calendar.month;
+
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.ViewType;
+import com.android.calendar.Event;
+import com.android.calendar.EventLoader;
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.Locale;
+
+public class FullMonthView extends MiniMonthView {
+    static final String TAG = "FullMonthView";
+    static final boolean DEBUG = false;
+
+    private static final int BASE_CELL_HEIGHT = 100;
+    private static final int DAY_NUM_TEXT_SIZE = 12;
+    private static final int BASE_SCREEN_HEIGHT = 800;
+    private static final int SCREEN_HEIGHT_ADJUSTMENT_FACTOR = 8;
+    protected static int MAX_TITLE_LENGTH = 25;
+    protected static int EVENT_TEXT_SIZE = 14;
+    protected static int EVENT_TEXT_BUFFER = 4;
+    protected static int EVENT_SWATCH_SIZE = 8;
+    protected static String ALL_DAY_STRING;
+
+    protected int[] mEventDayCounts = new int[mEventNumDays];
+    protected ArrayList<ArrayList<Event>> mEventDayList = new ArrayList<ArrayList<Event>>();
+    protected static StringBuilder mStringBuilder = new StringBuilder(50);
+    // TODO recreate formatter when locale changes
+    protected static Formatter mFormatter = new Formatter(
+            mStringBuilder, Locale.getDefault());
+    protected Resources mRes;
+
+    protected Rect mSwatchRect = new Rect();
+
+    public FullMonthView(Context activity, CalendarController controller, EventLoader eventLoader) {
+        super(activity, controller, eventLoader);
+        mRes = getResources();
+        ALL_DAY_STRING = mRes.getString(R.string.edit_event_all_day_label);
+        mMonthDayTextSize = (int) (DAY_NUM_TEXT_SIZE * mScale);
+        EVENT_SWATCH_SIZE *= mScale;
+        EVENT_TEXT_SIZE *= mScale;
+        EVENT_TEXT_BUFFER *= mScale;
+        mMonthNameSpace = 0;
+        mGoToView = ViewType.DETAIL;
+    }
+
+    /**
+     * Draw the names of the month on the left.
+     */
+    @Override
+    protected void drawMonthNames(Canvas canvas, Paint p) {
+
+    }
+
+    /**
+     * Draw the grid lines for the calendar
+     * @param canvas The canvas to draw on.
+     * @param p The paint used for drawing.
+     */
+    @Override
+    protected void drawGrid(Canvas canvas, Paint p) {
+        p.setColor(mMonthGridLineColor);
+        p.setAntiAlias(false);
+
+        final int width = mWidth + mMonthNameSpace;
+        final int height = mHeight;
+
+        int count = 0;
+        int y = mWeekOffset;
+        if (y > mCellHeight) {
+            y -= mCellHeight;
+        }
+        while (y <= height) {
+            canvas.drawLine(mMonthNameSpace, y, width, y, p);
+            // Compute directly to avoid rounding errors
+            count++;
+            y = count * height / mNumWeeksDisplayed + mWeekOffset - 1;
+        }
+
+        int x = mMonthNameSpace;
+        count = 0;
+        while (x <= width) {
+            canvas.drawLine(x, WEEK_GAP, x, height, p);
+            count++;
+            // Compute directly to avoid rounding errors
+            x = count * mWidth / 7 + mBorder - 1 + mMonthNameSpace;
+        }
+    }
+
+    @Override
+    protected void drawBox(int day, int row, int column, Canvas canvas, Paint p,
+            Rect r, boolean isLandscape) {
+        int julianDay = Time.getJulianDay(mDrawingDay.toMillis(true), mDrawingDay.gmtoff);
+
+        // Check if we're in a light or dark colored month
+        boolean colorSameAsCurrent = ((mDrawingDay.month & 1) == 0) == mIsEvenMonth;
+        // We calculate the position relative to the total size
+        // to avoid rounding errors.
+        int y = row * mHeight / mNumWeeksDisplayed + mWeekOffset;
+        int x = column * mWidth / 7 + mBorder + mMonthNameSpace;
+
+        r.left = x;
+        r.top = y;
+        r.right = x + mCellWidth;
+        r.bottom = y + mCellHeight;
+
+        // Draw the cell contents (excluding monthDay number)
+        if (mDrawingSelected) {
+            if (mSelectionMode == SELECTION_SELECTED) {
+                mBoxSelected.setBounds(r);
+                mBoxSelected.draw(canvas);
+            } else if (mSelectionMode == SELECTION_PRESSED) {
+                mBoxPressed.setBounds(r);
+                mBoxPressed.draw(canvas);
+            } else {
+                mBoxLongPressed.setBounds(r);
+                mBoxLongPressed.draw(canvas);
+            }
+        } else {
+            // Adjust cell boundaries to compensate for the different border
+            // style.
+            r.top--;
+            if (column != 0) {
+                r.left--;
+            }
+            p.setStyle(Style.FILL);
+            if (mDrawingToday) {
+                p.setColor(mMonthTodayBgColor);
+            } else if (!colorSameAsCurrent) {
+                p.setColor(mMonthOtherMonthBgColor);
+            } else {
+                p.setColor(mMonthBgColor);
+            }
+            canvas.drawRect(r, p);
+        }
+
+
+
+        p.setStyle(Paint.Style.FILL);
+        p.setAntiAlias(true);
+        p.setTypeface(null);
+
+        drawEvents(julianDay, canvas, r, p, colorSameAsCurrent);
+
+        // Draw the monthDay number
+        p.setTextSize(mMonthDayTextSize);
+
+        if (mDrawingToday && !mDrawingSelected) {
+            p.setColor(mMonthTodayNumberColor);
+        } else if (Utils.isSunday(column, mStartDayOfWeek)) {
+            p.setColor(mMonthSundayColor);
+        } else if (Utils.isSaturday(column, mStartDayOfWeek)) {
+            p.setColor(mMonthSaturdayColor);
+        } else {
+            p.setColor(mMonthDayNumberColor);
+        }
+
+        // bolds the day if there's an event that day
+        int julianOffset = julianDay - mFirstEventJulianDay;
+        if (julianOffset >= 0 && julianOffset < mEventNumDays) {
+            p.setFakeBoldText(mEventDays[julianOffset]);
+        } else {
+            p.setFakeBoldText(false);
+        }
+        /* Drawing of day number is done here
+         * easy to find tags draw number draw day*/
+        p.setTextAlign(Paint.Align.LEFT);
+        // center of text
+        // TODO figure out why it's not actually centered
+        int textX = x + TEXT_TOP_MARGIN;
+        // bottom of text
+        int textY = y + TEXT_TOP_MARGIN + mMonthDayTextSize;
+        canvas.drawText(String.valueOf(mDrawingDay.monthDay), textX, textY, p);
+    }
+
+    // Create and draw the event busybits for this day
+    @Override
+    protected void drawEvents(int date, Canvas canvas, Rect rect, Paint p, boolean drawBg) {
+        int julianOffset = date - mFirstEventJulianDay;
+        if (julianOffset < 0 || julianOffset >= mEventDayList.size()) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Drawing event for " + date);
+        }
+
+        // We do / 4 because the text is slightly smaller than it's size
+        int halfDiff = (EVENT_TEXT_SIZE - EVENT_SWATCH_SIZE) / 4;
+        // The point at which to start drawing the event text
+        int bot = rect.top + TEXT_TOP_MARGIN + mMonthDayTextSize + EVENT_TEXT_SIZE
+                + EVENT_TEXT_BUFFER;
+        int left = rect.left + EVENT_SWATCH_SIZE + 2 * EVENT_TEXT_BUFFER;
+        int swatchTop = bot - EVENT_SWATCH_SIZE - halfDiff;
+        int swatchBot = bot - halfDiff;
+        int swatchLeft = rect.left + EVENT_TEXT_BUFFER;
+        int swatchRight = swatchLeft + EVENT_SWATCH_SIZE;
+
+        Rect swatchRect = mSwatchRect;
+
+        p.setTextAlign(Paint.Align.LEFT);
+        p.setTextSize(EVENT_TEXT_SIZE);
+        int textHeight = (int) (-p.ascent());
+        // How far we move down before drawing the next line of text
+        int stepHeight = textHeight + EVENT_TEXT_BUFFER;
+        // How far to move down between events (2 lines)
+        int doubleStepHeight = 2 * stepHeight;
+        // This is the height past which we cannot fit an event and the 'x more
+        // events' message in the cell.
+        int maxEventHeight = rect.top + mCellHeight - doubleStepHeight - (int)p.descent();
+        // This is the height past which we cannot fit one more event
+        int maxAllEventsHeight = rect.top + mCellHeight - stepHeight - (int)p.descent();
+
+        ArrayList<Event> dayEvents = mEventDayList.get(julianOffset);
+        int numEvents = dayEvents.size();
+        int eventsDisplayed = 0;
+        int j = 0;
+        // Keep drawing while we have room for 'more events' or all the events
+        for (int i = 0; i < numEvents && (bot < maxEventHeight ||
+                (numEvents - eventsDisplayed - j <= 1 && bot < maxAllEventsHeight)); i++) {
+            if (mDrawingToday) {
+                long millis = System.currentTimeMillis();
+                while (i + j < numEvents && dayEvents.get(i + j).endMillis < millis) {
+                    j++;
+                }
+            }
+            if (i + j >= numEvents) {
+                return;
+            }
+            Event event = dayEvents.get(i + j);
+            int color = event.color;
+            swatchRect.set(swatchLeft, swatchTop, swatchRight, swatchBot);
+            p.setColor(color);
+            canvas.drawRect(swatchRect, p);
+            CharSequence disp = event.title;
+            if (disp.length() > MAX_TITLE_LENGTH) {
+                disp = disp.subSequence(0, MAX_TITLE_LENGTH);
+            }
+            p.setColor(mMonthDayNumberColor);
+            if (mDrawingToday && !mDrawingSelected) {
+                p.setColor(mMonthTodayNumberColor);
+            } else {
+                p.setColor(mMonthDayNumberColor);
+            }
+            canvas.drawText(disp.toString(), left, bot, p);
+
+            // Have to clear out the sb or it will keep appending forever
+            mStringBuilder.setLength(0);
+            if (!event.allDay) {
+                disp = DateUtils.formatDateRange(mContext, mFormatter, event.startMillis,
+                        event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
+                        Utils.getTimeZone(mContext, null)).toString();
+            } else {
+                disp = DateUtils.formatDateRange(mContext, mFormatter, event.startMillis,
+                        event.endMillis, DateUtils.FORMAT_ABBREV_ALL, Time.TIMEZONE_UTC).toString();
+            }
+
+            bot += stepHeight;
+            if (mDrawingToday && !mDrawingSelected) {
+                p.setColor(mMonthDayNumberColor);
+            } else {
+                p.setColor(mMonthOtherMonthDayNumberColor);
+            }
+            canvas.drawText(disp.toString(), left, bot, p);
+            eventsDisplayed++;
+
+            bot += stepHeight;
+            swatchTop += doubleStepHeight;
+            swatchBot += doubleStepHeight;
+        }
+        // Count all the events that are already over, we may not have counted
+        // them already if there were all day events
+        if (mDrawingToday) {
+            j = 0;
+            long millis = System.currentTimeMillis();
+            for (int i = 0; i < numEvents; i++) {
+                if (dayEvents.get(i).endMillis < millis) {
+                    j++;
+                }
+            }
+        }
+        if (numEvents > eventsDisplayed + j) {
+            int value = numEvents - eventsDisplayed - j;
+            String moreEvents = mRes.getQuantityString(R.plurals.gadget_more_events, value);
+            canvas.drawText(String.format(moreEvents, value), swatchLeft, bot, p);
+        }
+    }
+
+    @Override
+    public void reloadEvents() {
+        // Ensure everything is in the correct time zone. This may affect what
+        // day today is and can be called by the fragment when the db changes.
+        mUpdateTZ.run();
+
+        long millis = getFirstEventStartMillis();
+
+        // Load the days with events in the background
+//FRAG_TODO        mParentActivity.startProgressSpinner();
+        final long startMillis;
+        if (PROFILE_LOAD_TIME) {
+            startMillis = SystemClock.uptimeMillis();
+        } else {
+            // To avoid a compiler error that this variable might not be initialized.
+            startMillis = 0;
+        }
+
+        final ArrayList<Event> events = new ArrayList<Event>();
+        mEventLoader.loadEventsInBackground(mEventNumDays, events, millis, new Runnable() {
+            public void run() {
+                if (DEBUG) {
+                    Log.d(TAG, "found " + events.size() + " events");
+                }
+                if (mEventDayList.size() != mEventNumDays) {
+                    mEventDayList.clear();
+                    for (int i = 0; i < mEventNumDays; i++) {
+                        mEventDayList.add(new ArrayList<Event>());
+                    }
+                } else {
+                    for (int i = 0; i < mEventNumDays; i++) {
+                        mEventDayList.get(i).clear();
+                    }
+                }
+                mEvents = events;
+//FRAG_TODO                mParentActivity.stopProgressSpinner();
+                int numEvents = events.size();
+
+                long millis = getFirstEventStartMillis();
+                mFirstEventJulianDay = Time.getJulianDay(millis, mTempTime.gmtoff);
+                // Compute the new set of days with events
+                for (int i = 0; i < numEvents; i++) {
+                    Event event = events.get(i);
+                    int startDay = event.startDay - mFirstEventJulianDay;
+                    int endDay = event.endDay - mFirstEventJulianDay + 1;
+                    if (startDay < mEventNumDays || endDay >= 0) {
+                        if (startDay < 0) {
+                            startDay = 0;
+                        }
+                        if (startDay > mEventNumDays) {
+                            continue;
+                        }
+                        if (endDay < 0) {
+                            continue;
+                        }
+                        if (endDay > mEventNumDays) {
+                            endDay = mEventNumDays;
+                        }
+                        for (int j = startDay; j < endDay; j++) {
+                            mEventDayList.get(j).add(event);
+                        }
+                    }
+                }
+                mRedrawScreen = true;
+                invalidate();
+            }
+        }, null);
+    }
+
+    @Override
+    protected void drawingCalc(int width, int height) {
+        mDesiredCellHeight = BASE_CELL_HEIGHT + Math.max(height - BASE_SCREEN_HEIGHT, 0)
+                / SCREEN_HEIGHT_ADJUSTMENT_FACTOR;
+        super.drawingCalc(width, height);
+        mEventDayCounts = new int[mEventNumDays];
+    }
+}
diff --git a/src/com/android/calendar/month/MiniMonthView.java b/src/com/android/calendar/month/MiniMonthView.java
new file mode 100644
index 0000000..2ed4d01
--- /dev/null
+++ b/src/com/android/calendar/month/MiniMonthView.java
@@ -0,0 +1,1356 @@
+/*
+ * Copyright (C) 2010 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.calendar.month;
+
+import com.android.calendar.CalendarController;
+import com.android.calendar.CalendarController.EventType;
+import com.android.calendar.CalendarController.ViewType;
+import com.android.calendar.Event;
+import com.android.calendar.EventGeometry;
+import com.android.calendar.EventLoader;
+import com.android.calendar.MonthFragment;
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.PopupWindow;
+
+import java.util.ArrayList;
+
+public class MiniMonthView extends View implements View.OnCreateContextMenuListener,
+        MonthFragment.MonthViewInterface {
+
+    private static final String TAG = "MiniMonthView";
+    private static final boolean DEBUG = false;
+
+    protected static final boolean PROFILE_LOAD_TIME = false;
+
+    // TODO Move to a more general location
+    public static final int MIN_YEAR = 1970;
+    public static final int MAX_YEAR = 2036;
+    public static final int MIN_WEEK = MIN_YEAR * 52;
+    public static final int MAX_WEEK = (MAX_YEAR + 1) * 52;
+
+    protected static final int MIN_NUM_WEEKS = 3;
+    protected static final int MAX_NUM_WEEKS = 16;
+    protected static final int DEFAULT_NUM_WEEKS = 10;
+
+    // The number of days before and after the visible range that we should
+    // query for events. This lets us display DNA for a couple weeks when users
+    // start scrolling.
+    private static final int EVENT_QUERY_BUFFER_DAYS = 14;
+
+    protected static float mScale = 0; // Used for supporting different screen densities
+    protected static int WEEK_GAP = 0;
+    protected static int MONTH_DAY_GAP = 1;
+    protected static float HOUR_GAP = 0f;
+    protected static float MIN_EVENT_HEIGHT = 2f;
+    protected static int MONTH_NAME_TEXT_SIZE = 16;
+    protected static int WEEK_BANNER_HEIGHT = 17;
+    protected static int WEEK_TEXT_SIZE = 15;
+    protected static int WEEK_TEXT_PADDING = 3;
+    protected static int EVENT_DOT_TOP_MARGIN = 5;
+    protected static int EVENT_DOT_LEFT_MARGIN = 7;
+    protected static int EVENT_DOT_W_H = 10;
+    protected static int TEXT_TOP_MARGIN = 7;
+    protected static int BUSY_BITS_WIDTH = 6;
+    protected static int BUSY_BITS_MARGIN = 4;
+    protected static int DAY_NUMBER_OFFSET = 10;
+    protected static int VERTICAL_FLING_THRESHOLD = 50;
+    protected static int INITIAL_FLING_DIVISOR = 160;
+
+    protected static final int MENU_AGENDA = 2;
+    protected static final int MENU_DAY = 3;
+    protected static final int MENU_EVENT_CREATE = 6;
+
+    // These are non-static to allow subclasses to override in a second view
+    protected int mMonthNamePadding = 6;
+    protected int mMonthNameSpace = 12 + 2 * mMonthNamePadding;
+    protected int mDesiredCellHeight = 40;
+    protected int mMonthDayTextSize = 20;
+    protected int mFlingDivisor;
+
+    // The number of days worth of events to load
+    protected int mEventNumDays = 7 * (DEFAULT_NUM_WEEKS + 4);
+    // Which day of the week the display starts on
+    protected int mStartDayOfWeek;
+    // How many weeks to display at a time
+    protected int mNumWeeksDisplayed = DEFAULT_NUM_WEEKS;
+    // Which week position the selected day should snap to, this focuses the
+    // user's currently selected day about 1/3 of the way down the view.
+    protected int mFocusWeek = DEFAULT_NUM_WEEKS / 3;
+    // The top left day displayed. Drawing and selecting use this as a reference
+    // point.
+    protected Time mFirstDay;
+    // The current day being drawn to the canvas
+    protected Time mDrawingDay;
+    // Whether the current day being drawn is today
+    protected boolean mDrawingToday;
+    // Whether the current day being drawn is selected
+    protected boolean mDrawingSelected;
+    // The distance in pixels to offset the y position of the weeks
+    protected int mWeekOffset = 0;
+
+    // Height of a single day
+    protected int mCellHeight;
+    // Width of a single day
+    protected int mCellWidth;
+    // height of the view
+    protected int mHeight;
+    // width of the view
+    protected int mWidth;
+    // The number of pixels to leave as a buffer around the outside of the
+    // view.
+    protected int mBorder;
+    // Whether or not a touch event will select the day it's on
+    protected boolean mSelectDay;
+    // The y-offset when a touch event occurred
+    protected int mInitialOffset;
+
+    protected GestureDetector mGestureDetector;
+    // Handles flings, GoTos, and snapping to a week
+    protected GoToScroll mGoToScroll = new GoToScroll();
+
+    // The current local time on the device
+    protected Time mToday;
+    // The time being focused on, typically the first day in the week
+    // that is in the mFocusWeek position
+    protected Time mViewCalendar;
+    protected Time mSelectedDay;
+    protected Time mSavedTime; // the time when we entered this view
+    protected boolean mIsEvenMonth; // whether today is in an even numbered month
+
+    // This Time object is used to set the time for the other Month view.
+    protected Time mOtherViewCalendar;
+
+    // This Time object is used for temporary calculations and is allocated
+    // once to avoid extra garbage collection
+    protected Time mTempTime;
+
+    protected Drawable mBoxSelected;
+    protected Drawable mBoxPressed;
+    protected Drawable mBoxLongPressed;
+
+    protected Resources mResources;
+    protected Context mContext;
+    protected CalendarController mController;
+    protected final EventGeometry mEventGeometry;
+
+    // Pre-allocate and reuse
+    protected Rect mRect = new Rect();
+
+    //An array of which days have events for quick reference
+    protected boolean[] mEventDays = new boolean[mEventNumDays];
+
+    protected PopupWindow mPopup;
+    protected View mPopupView;
+    protected static final int POPUP_HEIGHT = 100;
+    protected int mPreviousPopupHeight;
+    protected static final int POPUP_DISMISS_DELAY = 3000;
+
+    // For drawing to an off-screen Canvas
+    protected Bitmap mBitmap;
+    protected Canvas mCanvas;
+    protected boolean mRedrawScreen = true;
+    protected Rect mBitmapRect = new Rect();
+    protected RectF mRectF = new RectF();
+    protected Path mMonthNamePath = new Path();
+
+    protected boolean mAnimating;
+    protected boolean mOnFlingCalled = false;
+    protected boolean mScrolling = false;
+
+    protected boolean mShowDNA = false;
+    protected int mGoToView = ViewType.CURRENT;
+
+    // Bitmap caches.
+    // These improve performance by minimizing calls to NinePatchDrawable.draw() for common
+    // drawables for day backgrounds.
+    // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
+    protected SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4);
+
+    protected ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
+
+    /**
+     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+     */
+    protected static final int SELECTION_HIDDEN = 0;
+    protected static final int SELECTION_PRESSED = 1;
+    protected static final int SELECTION_SELECTED = 2;
+    protected static final int SELECTION_LONGPRESS = 3;
+
+    protected int mSelectionMode = SELECTION_HIDDEN;
+
+    /**
+     * The first Julian day for which we have event data.
+     */
+    protected int mFirstEventJulianDay;
+
+    protected final EventLoader mEventLoader;
+
+    protected ArrayList<Event> mEvents = new ArrayList<Event>();
+
+    protected Drawable mTodayBackground;
+
+    // Cached colors
+    // TODO rename colors here and in xml to reflect how they are used
+    // Current months have the same even/odd-ness as the month of the current
+    // date. Other months don't.
+    // Color of grid lines
+    protected int mMonthGridLineColor;
+    // Not currently used
+    protected int mMonthWeekBannerColor;
+    // Not currently used
+    protected int mMonthOtherMonthBannerColor;
+    // Text color for day in other months
+    protected int mMonthOtherMonthDayNumberColor;
+    // Text color for day in current months
+    protected int mMonthDayNumberColor;
+    // Text color for today's day number
+    protected int mMonthTodayNumberColor;
+    // Text color for Saturday day numbers
+    protected int mMonthSaturdayColor;
+    // Text color for Sunday day numbers
+    protected int mMonthSundayColor;
+    // Color of the "DNA" event squares
+    protected int mMonthBusybitsColor;
+    // Background color for 'other' months
+    protected int mMonthOtherMonthBgColor;
+    // Background color for 'current' months
+    protected int mMonthBgColor;
+    // Background color for 'today'
+    protected int mMonthTodayBgColor;
+
+    protected Runnable mUpdateTZ = new Runnable() {
+        @Override
+        public void run() {
+            String tz = Utils.getTimeZone(mContext, this);
+
+            // These fields we want to keep the same day
+            mFirstDay.timezone = tz;
+            mFirstDay.normalize(true);
+            mDrawingDay.timezone = tz;
+            mDrawingDay.normalize(true);
+            mViewCalendar.timezone = tz;
+            mViewCalendar.normalize(true);
+            mSelectedDay.timezone = tz;
+            mSelectedDay.normalize(true);
+            mSavedTime.timezone = tz;
+            mSavedTime.normalize(true);
+            mOtherViewCalendar.timezone = tz;
+            mOtherViewCalendar.normalize(true);
+            mTempTime.timezone = tz;
+            mTempTime.normalize(true);
+
+            // These fields we want to keep the same time but allow to switch
+            // days
+            mToday.switchTimezone(tz);
+            mToday.normalize(true);
+
+            invalidate();
+        }
+    };
+
+    public MiniMonthView(Context activity, CalendarController controller, EventLoader eventLoader) {
+        super(activity);
+        if (mScale == 0) {
+            mScale = getContext().getResources().getDisplayMetrics().density;
+           if (mScale != 1) {
+                    WEEK_GAP *= mScale;
+                    MONTH_DAY_GAP *= mScale;
+                    HOUR_GAP *= mScale;
+                    mDesiredCellHeight *= mScale;
+                    mMonthDayTextSize *= mScale;
+                    WEEK_BANNER_HEIGHT *= mScale;
+                    WEEK_TEXT_SIZE *= mScale;
+                    WEEK_TEXT_PADDING *= mScale;
+                    EVENT_DOT_TOP_MARGIN *= mScale;
+                    EVENT_DOT_LEFT_MARGIN *= mScale;
+                    EVENT_DOT_W_H *= mScale;
+                    TEXT_TOP_MARGIN *= mScale * (mShowDNA ? 1 : 0);
+                    VERTICAL_FLING_THRESHOLD *= mScale;
+                    MIN_EVENT_HEIGHT *= mScale;
+                    // The boolean check makes sure text is centered whether or not DNA view is on
+                    BUSY_BITS_WIDTH *= mScale * (mShowDNA ? 1 : 0);
+                    BUSY_BITS_MARGIN *= mScale * (mShowDNA ? 1 : 0);
+                    DAY_NUMBER_OFFSET *= mScale;
+                    MONTH_NAME_TEXT_SIZE *= mScale;
+                    mMonthNamePadding *= mScale;
+                    mMonthNameSpace = MONTH_NAME_TEXT_SIZE + 2 * mMonthNamePadding;
+                }
+            }
+
+        mEventLoader = eventLoader;
+        mController = controller;
+        mEventGeometry = new EventGeometry();
+        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
+        mEventGeometry.setHourGap(HOUR_GAP);
+        init(activity);
+    }
+
+    private void init(Context activity) {
+        setFocusable(true);
+        setClickable(true);
+        setOnCreateContextMenuListener(this);
+        mContext = activity;
+
+        String tz = Utils.getTimeZone(activity, mUpdateTZ);
+        mViewCalendar = new Time(tz);
+        mFirstDay = new Time(tz);
+        mDrawingDay = new Time(tz);
+        mSelectedDay = new Time(tz);
+        mSavedTime = new Time(tz);
+        mToday = new Time(tz);
+        mOtherViewCalendar = new Time(tz);
+        mTempTime = new Time(tz);
+
+        long now = System.currentTimeMillis();
+        mViewCalendar.set(now);
+        mViewCalendar.monthDay = 1;
+        long millis = mViewCalendar.normalize(true /* ignore DST */);
+        mFirstEventJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
+        mStartDayOfWeek = Utils.getFirstDayOfWeek(activity);
+        mViewCalendar.set(now);
+        makeFirstDayOfWeek(mViewCalendar);
+        // The first week is mFocusWeek weeks earlier than the current week
+        mFirstDay.set(now - DateUtils.WEEK_IN_MILLIS * mFocusWeek);
+        makeFirstDayOfWeek(mFirstDay);
+        mToday.set(System.currentTimeMillis());
+        mIsEvenMonth = (mToday.month & 1) == 0;
+
+        mResources = activity.getResources();
+        mBoxSelected = mResources.getDrawable(R.drawable.month_view_selected);
+        mBoxPressed = mResources.getDrawable(R.drawable.month_view_pressed);
+        mBoxLongPressed = mResources.getDrawable(R.drawable.month_view_longpress);
+
+        mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
+
+        // Cache color lookups
+        Resources res = getResources();
+        mMonthGridLineColor = res.getColor(R.color.month_grid_lines);
+        mMonthWeekBannerColor = res.getColor(R.color.month_week_banner);
+        mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner);
+        mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number);
+        mMonthDayNumberColor = res.getColor(R.color.month_day_number);
+        mMonthTodayNumberColor = res.getColor(R.color.month_today_number);
+        mMonthSaturdayColor = res.getColor(R.color.month_saturday);
+        mMonthSundayColor = res.getColor(R.color.month_sunday);
+        mMonthBusybitsColor = res.getColor(R.color.month_busybits);
+        mMonthOtherMonthBgColor = res.getColor(R.color.month_other_bgcolor);
+        mMonthBgColor = res.getColor(R.color.month_bgcolor);
+        mMonthTodayBgColor = res.getColor(R.color.month_today_bgcolor);
+
+        mGestureDetector = new GestureDetector(getContext(),
+                new GestureDetector.SimpleOnGestureListener() {
+            int mScrollOffset;
+            @Override
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                    float velocityY) {
+                // The user might do a slow "fling" after touching the screen
+                // and we don't want the long-press to pop up a context menu.
+                // Setting mLaunchDayView to false prevents the long-press.
+                mSelectDay = false;
+                mSelectionMode = SELECTION_HIDDEN;
+                return doFling(e1, e2, velocityX, velocityY);
+            }
+
+            @Override
+            public boolean onDown(MotionEvent e) {
+                mInitialOffset = mWeekOffset;
+                mSelectDay = true;
+                mOnFlingCalled = false;
+                mScrollOffset = 0;
+                getHandler().removeCallbacks(mGoToScroll);
+                return true;
+            }
+
+            @Override
+            public void onShowPress(MotionEvent e) {
+                // Highlight the selected day.
+                long millis = getSelectedMillisFor((int) e.getX(), (int) e.getY());
+                mSelectedDay.set(millis);
+                mSelectionMode = SELECTION_PRESSED;
+                mRedrawScreen = true;
+                invalidate();
+            }
+
+            @Override
+            public void onLongPress(MotionEvent e) {
+                // If mLaunchDayView is true, then we haven't done any scrolling
+                // after touching the screen, so allow long-press to proceed
+                // with popping up the context menu.
+                if (mSelectDay) {
+                    mSelectDay = false;
+                    mSelectionMode = SELECTION_LONGPRESS;
+                    mRedrawScreen = true;
+                    invalidate();
+                    performLongClick();
+                }
+            }
+
+            @Override
+            public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                    float distanceX, float distanceY) {
+                // Use the distance from the current point to the initial touch instead
+                // of deltaX and deltaY to avoid accumulating floating-point rounding
+                // errors.  Also, we don't need floats, we can use ints.
+                int dY = (int) e1.getY() - (int) e2.getY() - mScrollOffset - mInitialOffset;
+                while (dY > mCellHeight) {
+                    dY -= mCellHeight;
+                    mScrollOffset += mCellHeight;
+                    mFirstDay.monthDay += 7;
+                }
+                while (dY < -mCellHeight) {
+                    dY += mCellHeight;
+                    mScrollOffset -= mCellHeight;
+                    mFirstDay.monthDay -= 7;
+                }
+                mFirstDay.normalize(true);
+                mWeekOffset = -dY;
+
+                mScrolling = true;
+                mRedrawScreen = true;
+                invalidate();
+                return true;
+            }
+
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                if (mSelectDay) {
+                    mSelectionMode = SELECTION_SELECTED;
+                    mRedrawScreen = true;
+                    invalidate();
+                    mSelectDay = false;
+                    int x = (int) e.getX();
+                    int y = (int) e.getY();
+                    long millis = getSelectedMillisFor(x, y);
+
+                    mTempTime.set(millis);
+                    mController.sendEvent(this, EventType.GO_TO, mTempTime, null, -1,
+                            mGoToView);
+                }
+
+                return true;
+            }
+        });
+    }
+
+    private void makeFirstDayOfWeek(Time time) {
+        int dayOfWeek = time.weekDay;
+        if (mStartDayOfWeek == Time.SUNDAY) {
+            time.monthDay -= dayOfWeek;
+        } else if (mStartDayOfWeek == Time.MONDAY) {
+            if (dayOfWeek == Time.SUNDAY) {
+                dayOfWeek += 7;
+            }
+            time.monthDay -= (dayOfWeek - Time.MONDAY);
+        } else if (mStartDayOfWeek == Time.SATURDAY) {
+            if (dayOfWeek != Time.SATURDAY) {
+                dayOfWeek += 7;
+            }
+            time.monthDay -= (dayOfWeek - Time.SATURDAY);
+        }
+        time.normalize(true);
+    }
+
+    boolean doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        mSelectionMode = SELECTION_HIDDEN;
+        mOnFlingCalled = true;
+        int deltaX = (int) e2.getX() - (int) e1.getX();
+        int distanceX = Math.abs(deltaX);
+        int deltaY = (int) e2.getY() - (int) e1.getY();
+        int distanceY = Math.abs(deltaY);
+
+        if ((distanceY <= VERTICAL_FLING_THRESHOLD) || (distanceY < distanceX)) {
+            // TODO do we want to ignore user input if they aren't swiping mostly vertical?
+            return false;
+        }
+
+        // Continue scrolling vertically
+
+        Handler handler = getHandler();
+        if (handler != null) {
+            getHandler().removeCallbacks(mGoToScroll);
+        }
+        mGoToScroll.init((int) -velocityY / mFlingDivisor);
+        post(mGoToScroll);
+        return true;
+    }
+
+    // Encapsulates the code to continue the scrolling after the
+    // finger is lifted.  Instead of stopping the scroll immediately,
+    // the scroll continues for a given number of weeks and then snaps
+    // to be on a week.
+    private class GoToScroll implements Runnable {
+        int mWeeks;
+//        int mScrollCount;
+        Time mStartTime;
+        private static final int SCROLL_REPEAT_INTERVAL = 30;
+        private static final int MAX_FLING_DURATION_MILLIS = 512;
+        private static final int INITIAL_FLING_DURATION_MILLIS = 256;
+        private static final int FLING_DURATION_INCREMENT = 32;
+        private int mFlingDurationMillis = 256;
+        private long mInitialSystemTime;
+
+        public void init(int numWeeksToScroll) {
+
+            int currFirstWeek = mFirstDay.getWeekNumber() + 52 * mFirstDay.year;
+            int targetFirstWeek = currFirstWeek + numWeeksToScroll;
+            if (targetFirstWeek < MIN_WEEK) {
+                // Will send it to the first week of 1970
+                numWeeksToScroll = MIN_WEEK - currFirstWeek;
+            } else if (targetFirstWeek + mNumWeeksDisplayed - 1 > MAX_WEEK) {
+                // Will send it to the last week of 2036
+                numWeeksToScroll = MAX_WEEK - currFirstWeek - mNumWeeksDisplayed + 1;
+            }
+            mStartTime = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
+            mInitialOffset = mWeekOffset;
+            mWeeks = numWeeksToScroll;
+            mStartTime.set(mFirstDay);
+
+            int absNumWeeks = Math.abs(numWeeksToScroll);
+
+            mFlingDurationMillis = INITIAL_FLING_DURATION_MILLIS + absNumWeeks
+                    * FLING_DURATION_INCREMENT;
+            if (mFlingDurationMillis > MAX_FLING_DURATION_MILLIS) {
+                mFlingDurationMillis = MAX_FLING_DURATION_MILLIS;
+            }
+            mInitialSystemTime = System.currentTimeMillis();
+        }
+
+        // At each step we set the first day and the y offset for the next
+        // position in a 'smooth' scroll.
+        public void run() {
+            if (mCellHeight <= 0) {
+                // The view isn't done initializing yet, just set it to the current day
+                mWeekOffset = 0;
+                mScrolling = false;
+                mFirstDay.set(mStartTime.toMillis(true) + DateUtils.WEEK_IN_MILLIS * mWeeks);
+                return;
+            }
+            int elapsedTime = (int) (System.currentTimeMillis() - mInitialSystemTime);
+            if (elapsedTime > mFlingDurationMillis) {
+                elapsedTime = mFlingDurationMillis;
+            }
+            // Calculate it based on the start so we don't accumulate rounding
+            // errors
+            // We don't pre-compute elapsed/duration due to integer arithmetic
+            mFirstDay.set(mStartTime.toMillis(true) + DateUtils.WEEK_IN_MILLIS
+                    * (long) Math.floor(mWeeks * elapsedTime / mFlingDurationMillis));
+            mWeekOffset = (mWeeks * mCellHeight * elapsedTime / mFlingDurationMillis)
+                    % mCellHeight;
+            // Get the direction right and make it a smooth scroll from wherever
+            // we were before
+            mWeekOffset = -mWeekOffset +
+                (mInitialOffset - mInitialOffset * elapsedTime / mFlingDurationMillis);
+
+            if (mWeekOffset > mCellHeight) {
+                mWeekOffset -= mCellHeight;
+                mFirstDay.monthDay -= 7;
+            } else if (mWeekOffset < -mCellHeight) {
+                mWeekOffset += mCellHeight;
+                mFirstDay.monthDay += 7;
+            }
+            makeFirstDayOfWeek(mFirstDay);
+
+            if (elapsedTime < mFlingDurationMillis) {
+                postDelayed(this, SCROLL_REPEAT_INTERVAL);
+            } else {
+                // Done scrolling.
+                mWeekOffset = 0;
+                mScrolling = false;
+                reloadEvents();
+            }
+
+            mRedrawScreen = true;
+            invalidate();
+        }
+    }
+
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        MenuItem item;
+
+        final long startMillis = getSelectedTimeInMillis();
+        final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
+                | DateUtils.FORMAT_ABBREV_MONTH;
+
+        final String title = Utils.formatDateRange(mContext, startMillis, startMillis, flags);
+        menu.setHeaderTitle(title);
+
+        item = menu.add(0, MENU_DAY, 0, R.string.show_day_view);
+        item.setOnMenuItemClickListener(mContextMenuHandler);
+        item.setIcon(android.R.drawable.ic_menu_day);
+        item.setAlphabeticShortcut('d');
+
+        item = menu.add(0, MENU_AGENDA, 0, R.string.show_agenda_view);
+        item.setOnMenuItemClickListener(mContextMenuHandler);
+        item.setIcon(android.R.drawable.ic_menu_agenda);
+        item.setAlphabeticShortcut('a');
+
+        item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
+        item.setOnMenuItemClickListener(mContextMenuHandler);
+        item.setIcon(android.R.drawable.ic_menu_add);
+        item.setAlphabeticShortcut('n');
+    }
+
+    private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
+        public boolean onMenuItemClick(MenuItem item) {
+            switch (item.getItemId()) {
+                case MENU_DAY: {
+                    mController.sendEvent(this, EventType.GO_TO, mSelectedDay, null, -1,
+                            ViewType.DAY);
+                    break;
+                }
+                case MENU_AGENDA: {
+                    mController.sendEvent(this, EventType.GO_TO, mSelectedDay, null, -1,
+                            ViewType.AGENDA);
+                    break;
+                }
+                case MENU_EVENT_CREATE: {
+                    long startMillis = getSelectedTimeInMillis();
+                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
+                    mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
+                            startMillis, endMillis, 0, 0);
+                    break;
+                }
+                default: {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    public void reloadEvents() {
+        // Ensure everything is in the correct time zone. This may affect what
+        // day today is and can be called by the fragment when the db changes.
+        mUpdateTZ.run();
+
+        long millis = getFirstEventStartMillis();
+
+        // Load the days with events in the background
+//FRAG_TODO        mParentActivity.startProgressSpinner();
+        final long startMillis;
+        if (PROFILE_LOAD_TIME) {
+            startMillis = SystemClock.uptimeMillis();
+        } else {
+            // To avoid a compiler error that this variable might not be initialized.
+            startMillis = 0;
+        }
+
+        final ArrayList<Event> events = new ArrayList<Event>();
+        mEventLoader.loadEventsInBackground(mEventNumDays, events, millis, new Runnable() {
+            public void run() {
+                if (DEBUG) {
+                    Log.d(TAG, "found " + events.size() + " events");
+                }
+                mEvents = events;
+//FRAG_TODO                mParentActivity.stopProgressSpinner();
+                int numEvents = events.size();
+                // Clear out event days
+                for (int i = 0; i < mEventNumDays; i++) {
+                    mEventDays[i] = false;
+                }
+
+                long millis = getFirstEventStartMillis();
+                mFirstEventJulianDay = Time.getJulianDay(millis, mTempTime.gmtoff);
+                // Compute the new set of days with events
+                for (int i = 0; i < numEvents; i++) {
+                    Event event = events.get(i);
+                    int startDay = event.startDay - mFirstEventJulianDay;
+                    int endDay = event.endDay - mFirstEventJulianDay + 1;
+                    if (startDay < mEventNumDays || endDay >= 0) {
+                        if (startDay < 0) {
+                            startDay = 0;
+                        }
+                        if (startDay > mEventNumDays) {
+                            continue;
+                        }
+                        if (endDay < 0) {
+                            continue;
+                        }
+                        if (endDay > mEventNumDays) {
+                            endDay = mEventNumDays;
+                        }
+                        for (int j = startDay; j < endDay; j++) {
+                            mEventDays[j] = true;
+                        }
+                    }
+                }
+                mRedrawScreen = true;
+                invalidate();
+            }
+        }, null);
+    }
+
+    protected long getFirstEventStartMillis() {
+        Time eventsStart = mTempTime;
+        eventsStart.set(mFirstDay);
+        // Query is from 2 weeks before our first day to two weeks after our
+        // last day
+        eventsStart.monthDay -= EVENT_QUERY_BUFFER_DAYS;
+        eventsStart.hour = 0;
+        eventsStart.minute = 0;
+        eventsStart.second = 0;
+        return eventsStart.normalize(true /* ignore isDst */);
+    }
+
+    public void animationStarted() {
+        mAnimating = true;
+    }
+
+    public void animationFinished() {
+        mAnimating = false;
+        mRedrawScreen = true;
+        invalidate();
+    }
+
+    @Override
+    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+        drawingCalc(width, height);
+        // If the size changed, then we should rebuild the bitmaps...
+        clearBitmapCache();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // No need to hang onto the bitmaps...
+        clearBitmapCache();
+        if (mBitmap != null) {
+            mBitmap.recycle();
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mRedrawScreen) {
+            if (mCanvas == null) {
+                drawingCalc(getWidth(), getHeight());
+            }
+
+            // If we are zero-sized, the canvas will remain null so check again
+            if (mCanvas != null) {
+                // Clear the background
+                final Canvas bitmapCanvas = mCanvas;
+                bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+                doDraw(bitmapCanvas);
+                mRedrawScreen = false;
+            }
+        }
+
+        // If we are zero-sized, the bitmap will be null so guard against this
+        if (mBitmap != null) {
+            canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null);
+        }
+    }
+
+    protected void doDraw(Canvas canvas) {
+        boolean isLandscape = getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+
+        Paint p = new Paint();
+        Rect r = mRect;
+        int day = 0;
+        mDrawingDay.set(mFirstDay);
+        int lastWeek = mNumWeeksDisplayed;
+        int row = 0;
+        if (mWeekOffset > 0) {
+            day = -7;
+            mDrawingDay.monthDay -= 7;
+            mDrawingDay.normalize(true);
+            row = -1;
+        } else if (mWeekOffset < 0) {
+            lastWeek++;
+        }
+
+        for (; row < lastWeek; row++) {
+            for (int column = 0; column < 7; column++) {
+                setDrawingBooleans();
+                drawBox(day, row, column, canvas, p, r, isLandscape);
+                day++;
+                mDrawingDay.monthDay++;
+                mDrawingDay.normalize(true);
+            }
+        }
+        drawGrid(canvas, p);
+        drawMonthNames(canvas, p);
+        // TODO add in method for drawing week numbers
+    }
+
+    protected void setDrawingBooleans() {
+        mDrawingToday = false;
+        // Check if the date we're drawing is today
+        if (mDrawingDay.year == mToday.year && mDrawingDay.yearDay == mToday.yearDay) {
+            mDrawingToday = true;
+        }
+
+        mDrawingSelected = false;
+        // Check if we're drawing the selected day and if we should show
+        // it as selected.
+        if (mSelectionMode != SELECTION_HIDDEN) {
+            mDrawingSelected = mSelectedDay.year == mDrawingDay.year &&
+                    mSelectedDay.yearDay == mDrawingDay.yearDay;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int action = event.getAction();
+
+        if (mGestureDetector.onTouchEvent(event)) {
+            return true;
+        } else if (action == MotionEvent.ACTION_UP) {
+            int halfHeight = (mCellHeight / 2);
+            // slide to the nearest week if the fling wasn't fast enough to do do a proper scroll
+            if (mWeekOffset > halfHeight) {
+                mGoToScroll.init(-1);
+            } else if (mWeekOffset < -halfHeight) {
+                mGoToScroll.init(1);
+            } else {
+                mGoToScroll.init(0);
+            }
+            post(mGoToScroll);
+            return true;
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    private long getSelectedMillisFor(int x, int y) {
+        int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
+        int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
+        Time time = mTempTime;
+        time.set(mFirstDay);
+
+        // Compute the day number from the row and column.  If the row and
+        // column are in a different month from the current one, then the
+        // monthDay might be negative or it might be greater than the number
+        // of days in this month, but that is okay because the normalize()
+        // method will adjust the month (and year) if necessary.
+        time.monthDay += 7 * row + column;
+        return time.normalize(true);
+    }
+
+    /**
+     * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
+     *
+     * @param drawable the drawable we wish to render
+     * @param width the width of the resulting bitmap
+     * @param height the height of the resulting bitmap
+     * @return a new bitmap
+     */
+    private Bitmap createBitmap(Drawable drawable, int width, int height) {
+        // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
+        Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
+
+        // Draw the drawable into the bitmap at the origin.
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, width, height);
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
+     * Clears the bitmap cache. Generally only needed when the screen size changed.
+     */
+    private void clearBitmapCache() {
+        recycleAndClearBitmapCache(mDayBitmapCache);
+    }
+
+    private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) {
+        int size = bitmapCache.size();
+        for(int i = 0; i < size; i++) {
+            bitmapCache.valueAt(i).recycle();
+        }
+        bitmapCache.clear();
+
+    }
+
+    /**
+     * Draw the names of the month on the left.
+     */
+    protected void drawMonthNames(Canvas canvas, Paint p) {
+        Time tempTime = mTempTime;
+        tempTime.set(mFirstDay);
+        boolean isFirstDay = true;
+        Time lastDay = new Time(mFirstDay);
+        lastDay.monthDay += mNumWeeksDisplayed * 7 - 1;
+        if (mWeekOffset < 0) {
+            lastDay.monthDay += 7;
+        }
+        lastDay.normalize(true);
+        int row = 0;
+        if (mWeekOffset > 0) {
+            row = -1;
+            tempTime.monthDay -= 7;
+            tempTime.normalize(true);
+        }
+        p.setColor(mMonthDayNumberColor);
+        p.setFakeBoldText(false);
+        p.setAntiAlias(true);
+        p.setTextAlign(Paint.Align.LEFT);
+        p.setTextSize(mMonthDayTextSize);
+        do {
+            // Start at the first full week of this month
+            int weekDay = tempTime.weekDay;
+            if (weekDay != mStartDayOfWeek) {
+                int moveDay = mStartDayOfWeek;
+                if (moveDay < weekDay) {
+                    moveDay += 7;
+                }
+                moveDay -= weekDay;
+                tempTime.monthDay += moveDay;
+                tempTime.normalize(true);
+                row++;
+            }
+            // and end on the last week of the month
+            int endOfMonth;
+            if (tempTime.month == lastDay.month) {
+                endOfMonth = lastDay.monthDay;
+            } else {
+                endOfMonth = tempTime.getActualMaximum(Time.MONTH_DAY);
+            }
+            int weekDiff = (endOfMonth - tempTime.monthDay) / 7 + 1;
+            // Only display the name if 2 or more weeks are showing
+            if (weekDiff > 1) {
+                String monthName = tempTime.format("%b");
+                int textWidth = (int) p.measureText(monthName);
+                // Compute directly to avoid rounding errors
+                // This places the text at the center of the weeks where the
+                // first day of the week is in the current month
+                int top = row * mHeight / mNumWeeksDisplayed + mWeekOffset;
+                int bot = (row + weekDiff) * mHeight / mNumWeeksDisplayed + mWeekOffset;
+                if (top < 0) {
+                    top = 0;
+                }
+                if (bot > mHeight) {
+                    bot = mHeight;
+                }
+                // Have to flip the offset since it's from the bottom instead of
+                // from the top
+                int offset = mHeight - top - (bot - top + textWidth) / 2;
+                canvas.drawTextOnPath(monthName, mMonthNamePath, offset,
+                        -mMonthNamePadding, p);
+            }
+            // Move the row to the next month
+            row += (endOfMonth - tempTime.monthDay + 1) / 7;
+            isFirstDay = false;
+            // Move our time marker to the next month
+            tempTime.monthDay = endOfMonth + 1;
+            tempTime.normalize(true);
+        } while(tempTime.before(lastDay));
+
+    }
+
+    /**
+     * Draw the grid lines for the calendar
+     * @param canvas The canvas to draw on.
+     * @param p The paint used for drawing.
+     */
+    protected void drawGrid(Canvas canvas, Paint p) {
+        p.setColor(mMonthGridLineColor);
+        p.setAntiAlias(false);
+
+        final int width = mWidth + mMonthNameSpace;
+        final int height = mHeight;
+
+        int count = 0;
+        int y = mWeekOffset;
+        if (y > mCellHeight) {
+            y -= mCellHeight;
+        }
+        while (y <= height) {
+            canvas.drawLine(mMonthNameSpace, y, width, y, p);
+            // Compute directly to avoid rounding errors
+            count++;
+            y = count * height / mNumWeeksDisplayed + mWeekOffset - 1;
+        }
+
+        int x = mMonthNameSpace;
+        count = 0;
+        while (x <= width) {
+            canvas.drawLine(x, WEEK_GAP, x, height, p);
+            count++;
+            // Compute directly to avoid rounding errors
+            x = count * mWidth / 7 + mBorder - 1 + mMonthNameSpace;
+        }
+    }
+
+    protected void drawBox(int day, int row, int column, Canvas canvas, Paint p,
+            Rect r, boolean isLandscape) {
+
+        int julianDay = Time.getJulianDay(mDrawingDay.toMillis(true), mDrawingDay.gmtoff);
+
+        // Check if we're in a light or dark colored month
+        boolean colorSameAsCurrent = ((mDrawingDay.month & 1) == 0) == mIsEvenMonth;
+        // We calculate the position relative to the total size
+        // to avoid rounding errors.
+        int y = row * mHeight / mNumWeeksDisplayed + mWeekOffset;
+        int x = column * mWidth / 7 + mBorder + mMonthNameSpace;
+
+        r.left = x;
+        r.top = y;
+        r.right = x + mCellWidth;
+        r.bottom = y + mCellHeight;
+
+        // Draw the cell contents (excluding monthDay number)
+        if (mDrawingSelected) {
+            if (mSelectionMode == SELECTION_SELECTED) {
+                mBoxSelected.setBounds(r);
+                mBoxSelected.draw(canvas);
+            } else if (mSelectionMode == SELECTION_PRESSED) {
+                mBoxPressed.setBounds(r);
+                mBoxPressed.draw(canvas);
+            } else {
+                mBoxLongPressed.setBounds(r);
+                mBoxLongPressed.draw(canvas);
+            }
+        } else {
+            // Adjust cell boundaries to compensate for the different border
+            // style.
+            r.top--;
+            if (column != 0) {
+                r.left--;
+            }
+            p.setStyle(Style.FILL);
+            if (mDrawingToday) {
+                p.setColor(mMonthTodayBgColor);
+            } else if (!colorSameAsCurrent) {
+                p.setColor(mMonthOtherMonthBgColor);
+            } else {
+                p.setColor(mMonthBgColor);
+            }
+            canvas.drawRect(r, p);
+        }
+
+
+        // TODO Places events for that day
+        drawEvents(julianDay, canvas, r, p, colorSameAsCurrent);
+
+        // Draw the monthDay number
+        p.setStyle(Paint.Style.FILL);
+        p.setAntiAlias(true);
+        p.setTypeface(null);
+        p.setTextSize(mMonthDayTextSize);
+
+        if (!colorSameAsCurrent) {
+            if (Utils.isSunday(column, mStartDayOfWeek)) {
+                p.setColor(mMonthSundayColor);
+            } else if (Utils.isSaturday(column, mStartDayOfWeek)) {
+                p.setColor(mMonthSaturdayColor);
+            } else {
+                p.setColor(mMonthOtherMonthDayNumberColor);
+            }
+        } else {
+            if (mDrawingToday && !mDrawingSelected) {
+                p.setColor(mMonthTodayNumberColor);
+            } else if (Utils.isSunday(column, mStartDayOfWeek)) {
+                p.setColor(mMonthSundayColor);
+            } else if (Utils.isSaturday(column, mStartDayOfWeek)) {
+                p.setColor(mMonthSaturdayColor);
+            } else {
+                p.setColor(mMonthDayNumberColor);
+            }
+        }
+
+        // bolds the day if there's an event that day
+        int julianOffset = julianDay - mFirstEventJulianDay;
+        if (julianOffset >= 0 && julianOffset < mEventNumDays) {
+            p.setFakeBoldText(mEventDays[julianOffset]);
+        } else {
+            p.setFakeBoldText(false);
+        }
+        /*Drawing of day number is done here
+         *easy to find tags draw number draw day*/
+        p.setTextAlign(Paint.Align.CENTER);
+        // center of text
+        // TODO figure out why it's not actually centered
+        int textX = x + (mCellWidth - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH) / 2;
+        // bottom of text
+        int textY = y + (mCellHeight + mMonthDayTextSize - 1) / 2 /*+ TEXT_TOP_MARGIN*/;
+        canvas.drawText(String.valueOf(mDrawingDay.monthDay), textX, textY, p);
+    }
+
+    ///Create and draw the event busybits for this day
+    protected void drawEvents(int date, Canvas canvas, Rect rect, Paint p, boolean drawBg) {
+        if (!mShowDNA) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Drawing event for " + date);
+        }
+        // The top of the busybits section lines up with the top of the day number
+        int top = rect.top /*+ TEXT_TOP_MARGIN*/ + BUSY_BITS_MARGIN;
+        int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
+
+        Style oldStyle = p.getStyle();
+        int oldColor = p.getColor();
+
+        ArrayList<Event> events = mEvents;
+        int numEvents = events.size();
+        EventGeometry geometry = mEventGeometry;
+
+        RectF rf = mRectF;
+        rf.left = left;
+        rf.right = left + BUSY_BITS_WIDTH;
+        rf.bottom = rect.bottom - BUSY_BITS_MARGIN;
+        rf.top = top;
+        p.setStyle(Style.FILL);
+
+        if (drawBg) {
+            p.setColor(mMonthOtherMonthBgColor);
+        } else {
+            p.setColor(mMonthBgColor);
+        }
+        canvas.drawRect(rf, p);
+
+        for (int i = 0; i < numEvents; i++) {
+            Event event = events.get(i);
+            if (!geometry.computeEventRect(date, left, top, BUSY_BITS_WIDTH, event)) {
+                continue;
+            }
+            drawEventRect(rect, event, canvas, p);
+        }
+
+    }
+
+    // Draw busybits for a single event
+    protected RectF drawEventRect(Rect rect, Event event, Canvas canvas, Paint p) {
+
+        p.setColor(mMonthBusybitsColor);
+
+        int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
+        int bottom = rect.bottom - BUSY_BITS_MARGIN;
+
+        RectF rf = mRectF;
+        rf.top = event.top;
+        // Make sure we don't go below the bottom of the bb bar
+        rf.bottom = Math.min(event.bottom, bottom);
+        rf.left = left;
+        rf.right = left + BUSY_BITS_WIDTH;
+
+        canvas.drawRect(rf, p);
+
+        return rf;
+    }
+
+    public void setSelectedTime(Time time) {
+        if (DEBUG) {
+            Log.d(TAG, "Setting time to " + time);
+        }
+        // Save the selected time so that we can restore it later when we switch views.
+        mSavedTime.set(time);
+
+        mViewCalendar.set(time);
+        mViewCalendar.monthDay = 1;
+        long millis = mViewCalendar.normalize(true /* ignore DST */);
+        mViewCalendar.set(time);
+
+        Handler handler = getHandler();
+        if (handler != null) {
+            getHandler().removeCallbacks(mGoToScroll);
+        }
+        makeFirstDayOfWeek(mViewCalendar);
+        // Kick off a scroll to the selected day
+        mGoToScroll.init(mViewCalendar.getWeekNumber() - mFocusWeek - mFirstDay.getWeekNumber()
+                + 52 * (mViewCalendar.year - mFirstDay.year));
+        post(mGoToScroll);
+        mSelectedDay.set(time);
+
+
+        mSelectionMode = SELECTION_SELECTED;
+        mRedrawScreen = true;
+        invalidate();
+    }
+
+    public long getSelectedTimeInMillis() {
+        return mSelectedDay.normalize(true);
+    }
+
+    public Time getTime() {
+        return mViewCalendar;
+    }
+
+    public int getSelectionMode() {
+        return mSelectionMode;
+    }
+
+    public void setSelectionMode(int selectionMode) {
+        mSelectionMode = selectionMode;
+    }
+
+    protected void drawingCalc(int width, int height) {
+        mHeight = getMeasuredHeight();
+        mWidth = getMeasuredWidth() - mMonthNameSpace;
+        mNumWeeksDisplayed = mHeight / mDesiredCellHeight;
+        if (mNumWeeksDisplayed < MIN_NUM_WEEKS) {
+            mNumWeeksDisplayed = MIN_NUM_WEEKS;
+        } else if (mNumWeeksDisplayed > MAX_NUM_WEEKS) {
+            mNumWeeksDisplayed = MAX_NUM_WEEKS;
+        }
+        // This lets us query 2 weeks before and after the first visible day
+        mEventNumDays = 7 * (mNumWeeksDisplayed + 4);
+        mEventDays = new boolean[mEventNumDays];
+        mCellHeight = (height - (mNumWeeksDisplayed * WEEK_GAP)) / mNumWeeksDisplayed;
+        mFocusWeek = mNumWeeksDisplayed / 3;
+        mFlingDivisor = INITIAL_FLING_DIVISOR * MAX_NUM_WEEKS / mNumWeeksDisplayed;
+        // TODO use something other than mEventGeometry, which has rounding
+        // errors and is slow(ish)
+        mEventGeometry
+                .setHourHeight((mCellHeight - BUSY_BITS_MARGIN * 2) / 24.0f + 0.1f);
+        mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7;
+        mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2;
+
+        if (((mBitmap == null)
+                    || mBitmap.isRecycled()
+                    || (mBitmap.getHeight() != height)
+                    || (mBitmap.getWidth() != width))
+                && (width > 0) && (height > 0)) {
+            if (mBitmap != null) {
+                mBitmap.recycle();
+            }
+            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            mCanvas = new Canvas(mBitmap);
+        }
+
+        mBitmapRect.top = 0;
+        mBitmapRect.bottom = height;
+        mBitmapRect.left = 0;
+        mBitmapRect.right = width;
+        mMonthNamePath.reset();
+        mMonthNamePath.moveTo(mMonthNameSpace, height);
+        mMonthNamePath.lineTo(mMonthNameSpace, 0);
+        setSelectedTime(mSelectedDay);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        long duration = event.getEventTime() - event.getDownTime();
+
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+            if (mSelectionMode == SELECTION_HIDDEN) {
+                // Don't do anything unless the selection is visible.
+                break;
+            }
+
+            if (mSelectionMode == SELECTION_PRESSED) {
+                // This was the first press when there was nothing selected.
+                // Change the selection from the "pressed" state to the
+                // the "selected" state.  We treat short-press and
+                // long-press the same here because nothing was selected.
+                mSelectionMode = SELECTION_SELECTED;
+                mRedrawScreen = true;
+                invalidate();
+                break;
+            }
+
+            // Check the duration to determine if this was a short press
+            if (duration < ViewConfiguration.getLongPressTimeout()) {
+                mController.sendEvent(this, EventType.GO_TO, mSelectedDay, null, -1,
+                        mGoToView);
+            } else {
+                mSelectionMode = SELECTION_LONGPRESS;
+                mRedrawScreen = true;
+                invalidate();
+                performLongClick();
+            }
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mSelectionMode == SELECTION_HIDDEN) {
+            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
+                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                // Display the selection box but don't move or select it
+                // on this key press.
+                mSelectionMode = SELECTION_SELECTED;
+                mRedrawScreen = true;
+                invalidate();
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+                // Display the selection box but don't select it
+                // on this key press.
+                mSelectionMode = SELECTION_PRESSED;
+                mRedrawScreen = true;
+                invalidate();
+                return true;
+            }
+        }
+
+        mSelectionMode = SELECTION_SELECTED;
+        boolean redraw = false;
+
+        switch (keyCode) {
+            // TODO make month move correctly when selected week changes
+        case KeyEvent.KEYCODE_ENTER:
+            mController.sendEvent(this, EventType.GO_TO, mSelectedDay, null, -1, mGoToView);
+            return true;
+        case KeyEvent.KEYCODE_DPAD_UP:
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            redraw = true;
+            break;
+        }
+
+        if (redraw) {
+            mRedrawScreen = true;
+            invalidate();
+        }
+
+        return redraw;
+    }
+}
diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsFragment.java b/src/com/android/calendar/selectcalendars/SelectCalendarsFragment.java
new file mode 100644
index 0000000..9dd7b8d
--- /dev/null
+++ b/src/com/android/calendar/selectcalendars/SelectCalendarsFragment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 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.calendar.selectcalendars;
+
+import com.android.calendar.AsyncQueryService;
+import com.android.calendar.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Calendar.Calendars;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+
+public class SelectCalendarsFragment extends Fragment
+    implements AdapterView.OnItemClickListener {
+
+    private static final String TAG = "Calendar";
+    private static final String EXPANDED_KEY = "is_expanded";
+    private static final String IS_PRIMARY = "\"primary\"";
+    private static final String SELECTION = Calendars.SYNC_EVENTS + "=?";
+    private static final String[] SELECTION_ARGS = new String[] {"1"};
+
+    private static final String[] PROJECTION = new String[] {
+        Calendars._ID,
+        Calendars._SYNC_ACCOUNT,
+        Calendars.OWNER_ACCOUNT,
+        Calendars.DISPLAY_NAME,
+        Calendars.COLOR,
+        Calendars.SELECTED,
+        Calendars.SYNC_EVENTS,
+        "(" + Calendars._SYNC_ACCOUNT + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY,
+      };
+    private static final int COLUMN_ID = 0;
+    private static final int COLUMN_SYNC_ACCOUNT = 1;
+    private static final int COLUMN_OWNER_ACCOUNT = 2;
+    private static final int COLUMN_DISPLAY_NAME = 3;
+    private static final int COLUMN_COLOR = 4;
+    private static final int COLUMN_SELECTED = 5;
+    private static final int COLUMN_SYNC_EVENTS = 6;
+    private static int mUpdateToken;
+    private static int mQueryToken;
+
+    private View mView = null;
+    private Cursor mCursor = null;
+    private ListView mList;
+    private SelectCalendarsSimpleAdapter mAdapter;
+    private Activity mContext;
+    private AsyncQueryService mService;
+    private Object[] mTempRow = new Object[PROJECTION.length];
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+        mService = new AsyncQueryService(activity) {
+            @Override
+            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+                mCursor = cursor;
+                Log.d(TAG, "adding " + cursor.getCount() + " calendars.");
+                mAdapter.changeCursor(cursor);
+            }
+        };
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        Activity activity = getActivity();
+        mView = inflater.inflate(R.layout.select_calendars_fragment, null);
+        mList = (ListView)mView.findViewById(R.id.list);
+        return mView;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mAdapter = new SelectCalendarsSimpleAdapter(mContext, R.layout.mini_calendar_item, null);
+        mList.setAdapter(mAdapter);
+        mList.setOnItemClickListener(this);
+    }
+
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id)  {
+        if (mAdapter == null || mAdapter.getCount() <= position) {
+            return;
+        }
+        toggleVisibility(position);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mQueryToken = mService.getNextToken();
+        mService.startQuery(mQueryToken, null, Calendars.CONTENT_URI, PROJECTION, SELECTION,
+                SELECTION_ARGS, Calendars._SYNC_ACCOUNT);
+    }
+
+    /*
+     * Write back the changes that have been made.
+     */
+    public void toggleVisibility(int position) {
+        Log.d(TAG, "Toggling calendar at " + position);
+        mUpdateToken = mService.getNextToken();
+        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, mAdapter.getItemId(position));
+        ContentValues values = new ContentValues();
+        // Toggle the current setting
+        int visibility = mAdapter.getVisible(position)^1;
+        values.put(Calendars.SELECTED, visibility);
+        mService.startUpdate(mUpdateToken, null, uri, values, null, null, 0);
+        mAdapter.setVisible(position, visibility);
+    }
+}
diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java
new file mode 100644
index 0000000..2a4815c
--- /dev/null
+++ b/src/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 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.calendar.selectcalendars;
+
+import com.android.calendar.R;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Paint;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.provider.Calendar.Calendars;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+public class SelectCalendarsSimpleAdapter extends BaseAdapter implements ListAdapter {
+    private static final int SELECTED_BOX_BORDER = 4;
+    private static int COLOR_CHIP_SIZE = 30;
+    private RectShape r = new RectShape();
+
+    private LayoutInflater mInflater;
+    private int mLayout;
+    private CalendarRow[] mData;
+    private Cursor mCursor;
+    private int mRowCount = 0;
+
+    private int mIdColumn;
+    private int mNameColumn;
+    private int mColorColumn;
+    private int mSelectedColumn;
+
+    private class CalendarRow {
+        long id;
+        String displayName;
+        int color;
+        boolean selected;
+    }
+
+    public SelectCalendarsSimpleAdapter(Context context, int layout, Cursor c) {
+        super();
+        mLayout = layout;
+        initData(c);
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        COLOR_CHIP_SIZE *= context.getResources().getDisplayMetrics().density;
+        r.resize(COLOR_CHIP_SIZE, COLOR_CHIP_SIZE);
+    }
+
+    private void initData(Cursor c) {
+        if (mCursor != null && c != mCursor) {
+            mCursor.close();
+        }
+        if (c == null) {
+            mCursor = c;
+            mRowCount = 0;
+            mData = null;
+            return;
+        }
+        // TODO create a broadcast listener for ACTION_PROVIDER_CHANGED to update the cursor
+        mCursor = c;
+        mIdColumn = c.getColumnIndexOrThrow(Calendars._ID);
+        mNameColumn = c.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
+        mColorColumn = c.getColumnIndexOrThrow(Calendars.COLOR);
+        mSelectedColumn = c.getColumnIndexOrThrow(Calendars.SELECTED);
+
+        mRowCount = c.getCount();
+        mData = new CalendarRow[(c.getCount() + 2)];
+        c.moveToPosition(-1);
+        int p = 0;
+        while (c.moveToNext()) {
+            mData[p] = new CalendarRow();
+            mData[p].id = c.getLong(mIdColumn);
+            mData[p].displayName = c.getString(mNameColumn);
+            mData[p].color = c.getInt(mColorColumn);
+            mData[p].selected = c.getInt(mSelectedColumn) != 0;
+            p++;
+        }
+    }
+
+    public void changeCursor(Cursor c) {
+        initData(c);
+        notifyDataSetChanged();
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (position >= mRowCount) {
+            return null;
+        }
+        String name = mData[position].displayName;
+        boolean selected = mData[position].selected;
+        int color = mData[position].color;
+        View view;
+        if (convertView == null) {
+            view = mInflater.inflate(mLayout, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        View colorView = view.findViewById(R.id.color);
+        ShapeDrawable box = new ShapeDrawable(r);
+        Paint p = box.getPaint();
+        p.setColor(color);
+        if (selected) {
+            p.setStyle(Paint.Style.FILL_AND_STROKE);
+        } else {
+            p.setStyle(Paint.Style.STROKE);
+            p.setStrokeWidth(SELECTED_BOX_BORDER);
+        }
+
+        colorView.setBackgroundDrawable(box);
+        setText(view, R.id.calendar, name);
+        return view;
+    }
+
+    private static void setText(View view, int id, String text) {
+        if (TextUtils.isEmpty(text)) {
+            return;
+        }
+        TextView textView = (TextView) view.findViewById(id);
+        textView.setText(text);
+    }
+
+
+    /* (non-Javadoc)
+     * @see android.widget.Adapter#getCount()
+     */
+    public int getCount() {
+        return mRowCount;
+    }
+
+
+    /* (non-Javadoc)
+     * @see android.widget.Adapter#getItem(int)
+     */
+    public Object getItem(int position) {
+        if (position >= mRowCount) {
+            return null;
+        }
+        CalendarRow item = mData[position];
+        return item;
+    }
+
+
+    /* (non-Javadoc)
+     * @see android.widget.Adapter#getItemId(int)
+     */
+    public long getItemId(int position) {
+        if (position >= mRowCount) {
+            return 0;
+        }
+        return mData[position].id;
+    }
+
+    public void setVisible(int position, int visible) {
+        mData[position].selected = visible != 0;
+        notifyDataSetChanged();
+    }
+
+    public int getVisible(int position) {
+        return mData[position].selected ? 1 : 0;
+    }
+
+}
diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java
new file mode 100644
index 0000000..ba6c66e
--- /dev/null
+++ b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncAdapter.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 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.calendar.selectcalendars;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.shapes.RectShape;
+import android.provider.Calendar.Calendars;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+public class SelectCalendarsSyncAdapter extends BaseAdapter
+        implements ListAdapter, CompoundButton.OnCheckedChangeListener {
+    private static final String TAG = "SelCalsAdapter";
+    private static final int SELECTED_BOX_BORDER = 4;
+    private static int COLOR_CHIP_SIZE = 30;
+    private RectShape r = new RectShape();
+
+    private LayoutInflater mInflater;
+    private static final int LAYOUT = R.layout.calendar_sync_item;
+    private CalendarRow[] mData;
+    private HashMap<Integer, CalendarRow> mChanges = new HashMap<Integer, CalendarRow>();
+    private Cursor mCursor;
+    private int mRowCount = 0;
+
+    private int mIdColumn;
+    private int mNameColumn;
+    private int mColorColumn;
+    private int mSyncedColumn;
+
+    private final String mSyncedString;
+    private final String mNotSyncedString;
+
+    public class CalendarRow {
+        long id;
+        String displayName;
+        int color;
+        boolean synced;
+    }
+
+    public SelectCalendarsSyncAdapter(Context context, Cursor c) {
+        super();
+        initData(c);
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        COLOR_CHIP_SIZE *= context.getResources().getDisplayMetrics().density;
+        r.resize(COLOR_CHIP_SIZE, COLOR_CHIP_SIZE);
+        Resources res = context.getResources();
+        mSyncedString = res.getString(R.string.synced);
+        mNotSyncedString = res.getString(R.string.not_synced);
+    }
+
+    private void initData(Cursor c) {
+        if (c == null) {
+            mCursor = c;
+            mRowCount = 0;
+            mData = null;
+            return;
+        }
+
+        mCursor = c;
+        mIdColumn = c.getColumnIndexOrThrow(Calendars._ID);
+        mNameColumn = c.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
+        mColorColumn = c.getColumnIndexOrThrow(Calendars.COLOR);
+        mSyncedColumn = c.getColumnIndexOrThrow(Calendars.SYNC_EVENTS);
+
+        mRowCount = c.getCount();
+        mData = new CalendarRow[mRowCount];
+        c.moveToPosition(-1);
+        int p = 0;
+        while (c.moveToNext()) {
+            mData[p] = new CalendarRow();
+            mData[p].id = c.getLong(mIdColumn);
+            mData[p].displayName = c.getString(mNameColumn);
+            mData[p].color = c.getInt(mColorColumn);
+            mData[p].synced = c.getInt(mSyncedColumn) != 0;
+            p++;
+        }
+    }
+
+    public void changeCursor(Cursor c) {
+        initData(c);
+        notifyDataSetChanged();
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (position >= mRowCount) {
+            return null;
+        }
+        String name = mData[position].displayName;
+        boolean selected = mData[position].synced;
+        int color = mData[position].color;
+        View view;
+        if (convertView == null) {
+            view = mInflater.inflate(LAYOUT, parent, false);
+        } else {
+            view = convertView;
+        }
+
+        CheckBox cb = (CheckBox) view.findViewById(R.id.sync);
+        // This must be set to null in case the view was recycled
+        cb.setOnCheckedChangeListener(null);
+        cb.setChecked(selected);
+        cb.setTag(mData[position]);
+        cb.setOnCheckedChangeListener(this);
+
+        if (selected) {
+            setText(view, R.id.status, mSyncedString);
+        } else {
+            setText(view, R.id.status, mNotSyncedString);
+        }
+
+        View colorView = view.findViewById(R.id.color);
+
+        colorView.setBackgroundDrawable(Utils.getColorChip(color));
+
+        setText(view, R.id.calendar, name);
+        return view;
+    }
+
+    private static void setText(View view, int id, String text) {
+        if (TextUtils.isEmpty(text)) {
+            return;
+        }
+        TextView textView = (TextView) view.findViewById(id);
+        textView.setText(text);
+    }
+
+    public int getCount() {
+        return mRowCount;
+    }
+
+    public Object getItem(int position) {
+        if (position >= mRowCount) {
+            return null;
+        }
+        CalendarRow item = mData[position];
+        return item;
+    }
+
+    public long getItemId(int position) {
+        if (position >= mRowCount) {
+            return 0;
+        }
+        return mData[position].id;
+    }
+
+    public int getSynced(int position) {
+        return mData[position].synced ? 1 : 0;
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        CalendarRow row = (CalendarRow) buttonView.getTag();
+        row.synced = isChecked;
+
+        // There is some data loss in long -> int, but we should never see it in
+        // practice regarding calendar ids.
+        mChanges.put((int) row.id, row);
+    }
+
+    public HashMap getChanges() {
+        return mChanges;
+    }
+}
diff --git a/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java
new file mode 100644
index 0000000..56fa046
--- /dev/null
+++ b/src/com/android/calendar/selectcalendars/SelectCalendarsSyncFragment.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2010 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.calendar.selectcalendars;
+
+import com.android.calendar.AsyncQueryService;
+import com.android.calendar.R;
+import com.android.calendar.selectcalendars.SelectCalendarsSyncAdapter.CalendarRow;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Calendar;
+import android.provider.Calendar.Calendars;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+public class SelectCalendarsSyncFragment extends ListFragment
+        implements View.OnClickListener, LoaderManager.LoaderCallbacks<Cursor> {
+    private static final String TAG = "SelectCalendarSync";
+    private static final boolean DEBUG = false;
+
+    private static final String COLLATE_NOCASE = " COLLATE NOCASE";
+    private static final String SELECTION = Calendars._SYNC_ACCOUNT + "=? AND "
+            + Calendars._SYNC_ACCOUNT_TYPE + "=?";
+    // is primary lets us sort the user's main calendar to the top of the list
+    private static final String IS_PRIMARY = "\"primary\"";
+    private static final String SORT_ORDER = IS_PRIMARY + " DESC," + Calendars.DISPLAY_NAME
+            + COLLATE_NOCASE;
+
+    private static final String[] PROJECTION = new String[] { Calendars._ID,
+            Calendars._SYNC_ACCOUNT, Calendars.OWNER_ACCOUNT, Calendars.DISPLAY_NAME,
+            Calendars.COLOR, Calendars.SELECTED, Calendars.SYNC_EVENTS,
+            "(" + Calendars._SYNC_ACCOUNT + "=" + Calendars.OWNER_ACCOUNT + ") AS " + IS_PRIMARY, };
+    private static final int COLUMN_ID = 0;
+    private static final int COLUMN_SYNC_ACCOUNT = 1;
+    private static final int COLUMN_OWNER_ACCOUNT = 2;
+    private static final int COLUMN_DISPLAY_NAME = 3;
+    private static final int COLUMN_COLOR = 4;
+    private static final int COLUMN_SELECTED = 5;
+    private static final int COLUMN_SYNC_EVENTS = 6;
+
+    private static int mUpdateToken;
+    private static int mQueryToken;
+
+    private TextView mSyncStatus;
+    private Button mAccountsButton;
+    private Account mAccount;
+    private String[] mArgs = new String[2];
+    private AsyncQueryService mService;
+
+    public SelectCalendarsSyncFragment() {
+        Log.d(TAG, "Without bundle was created");
+    }
+
+    public SelectCalendarsSyncFragment(Bundle bundle) {
+        Log.d(TAG, "With bundle was created");
+        mAccount = new Account(
+                bundle.getString(Calendars.ACCOUNT_NAME), bundle.getString(Calendars.ACCOUNT_TYPE));
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.account_calendars, null);
+        mSyncStatus = (TextView) v.findViewById(R.id.account_status);
+        mSyncStatus.setVisibility(View.GONE);
+
+        mAccountsButton = (Button) v.findViewById(R.id.sync_settings);
+        mAccountsButton.setVisibility(View.GONE);
+        mAccountsButton.setOnClickListener(this);
+
+        return v;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        // Give some text to display if there is no data. In a real
+        // application this would come from a resource.
+        setEmptyText(getActivity().getText(R.string.no_syncable_calendars));
+        // Prepare the loader. Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (!ContentResolver.getMasterSyncAutomatically()
+                || !ContentResolver.getSyncAutomatically(mAccount, Calendar.AUTHORITY)) {
+            Resources res = getActivity().getResources();
+            mSyncStatus.setText(res.getString(R.string.acct_not_synced));
+            mSyncStatus.setVisibility(View.VISIBLE);
+            mAccountsButton.setText(res.getString(R.string.accounts));
+            mAccountsButton.setVisibility(View.VISIBLE);
+        } else {
+            mSyncStatus.setVisibility(View.GONE);
+            mAccountsButton.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mService = new AsyncQueryService(activity);
+
+        Bundle bundle = getArguments();
+        if (bundle != null && bundle.containsKey(Calendars.ACCOUNT_NAME)
+                && bundle.containsKey(Calendars.ACCOUNT_TYPE)) {
+            mAccount = new Account(bundle.getString(Calendars.ACCOUNT_NAME),
+                    bundle.getString(Calendars.ACCOUNT_TYPE));
+        }
+    }
+
+    @Override
+    public void onPause() {
+        HashMap<Integer, CalendarRow> changes = ((SelectCalendarsSyncAdapter) getListAdapter())
+                .getChanges();
+        if (changes != null && changes.size() > 0) {
+            for (CalendarRow row : changes.values()) {
+                int id = (int) row.id;
+                mService.cancelOperation(id);
+                // Use the full long id in case it makes a difference
+                Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, row.id);
+                ContentValues values = new ContentValues();
+                // Toggle the current setting
+                int synced = row.synced ? 1 : 0;
+                values.put(Calendars.SYNC_EVENTS, synced);
+                mService.startUpdate(id, null, uri, values, null, null, 0);
+            }
+            changes.clear();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        mArgs[0] = mAccount.name;
+        mArgs[1] = mAccount.type;
+        return new CursorLoader(
+                getActivity(), Calendars.CONTENT_URI, PROJECTION, SELECTION, mArgs, SORT_ORDER);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        SelectCalendarsSyncAdapter adapter = (SelectCalendarsSyncAdapter) getListAdapter();
+        if (adapter == null) {
+            adapter = new SelectCalendarsSyncAdapter(getActivity(), data);
+        } else {
+            adapter.changeCursor(data);
+        }
+        setListAdapter(adapter);
+    }
+
+    // Called when the Accounts button is pressed. Takes the user to the
+    // Accounts and Sync settings page.
+    @Override
+    public void onClick(View v) {
+        Intent intent = new Intent();
+        intent.setAction("android.settings.SYNC_SETTINGS");
+        getActivity().startActivity(intent);
+    }
+}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetModel.java b/src/com/android/calendar/widget/CalendarAppWidgetModel.java
new file mode 100644
index 0000000..dd57d61
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetModel.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2010 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.calendar.widget;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TimeZone;
+
+class CalendarAppWidgetModel {
+
+    private static final String TAG = CalendarAppWidgetModel.class.getSimpleName();
+
+    private static final boolean LOGD = false;
+
+    private String mHomeTZName;
+    private boolean mShowTZ;
+    /**
+     * {@link RowInfo} is a class that represents a single row in the widget. It
+     * is actually only a pointer to either a {@link DayInfo} or an
+     * {@link EventInfo} instance, since a row in the widget might be either a
+     * day header or an event.
+     */
+    static class RowInfo {
+
+        static final int TYPE_DAY = 0;
+
+        static final int TYPE_MEETING = 1;
+
+        /**
+         *  mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
+         */
+        final int mType;
+
+        /**
+         * If mType is TYPE_DAY, then mData is the index into day infos.
+         * Otherwise mType is TYPE_MEETING and mData is the index into event
+         * infos.
+         */
+        final int mIndex;
+
+        RowInfo(int type, int index) {
+            mType = type;
+            mIndex = index;
+        }
+    }
+
+    /**
+     * {@link EventInfo} is a class that represents an event in the widget. It
+     * contains all of the data necessary to display that event, including the
+     * properly localized strings and visibility settings.
+     */
+    static class EventInfo {
+        int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
+        String when;
+        int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
+        String where;
+        int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
+        String title;
+
+        long start;
+        long end;
+        boolean allDay;
+
+        public EventInfo() {
+            visibWhen = View.GONE;
+            visibWhere = View.GONE;
+            visibTitle = View.GONE;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("EventInfo [visibTitle=");
+            builder.append(visibTitle);
+            builder.append(", title=");
+            builder.append(title);
+            builder.append(", visibWhen=");
+            builder.append(visibWhen);
+            builder.append(", when=");
+            builder.append(when);
+            builder.append(", visibWhere=");
+            builder.append(visibWhere);
+            builder.append(", where=");
+            builder.append(where);
+            builder.append("]");
+            return builder.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (allDay ? 1231 : 1237);
+            result = prime * result + (int) (end ^ (end >>> 32));
+            result = prime * result + (int) (start ^ (start >>> 32));
+            result = prime * result + ((title == null) ? 0 : title.hashCode());
+            result = prime * result + visibTitle;
+            result = prime * result + visibWhen;
+            result = prime * result + visibWhere;
+            result = prime * result + ((when == null) ? 0 : when.hashCode());
+            result = prime * result + ((where == null) ? 0 : where.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            EventInfo other = (EventInfo) obj;
+            if (allDay != other.allDay)
+                return false;
+            if (end != other.end)
+                return false;
+            if (start != other.start)
+                return false;
+            if (title == null) {
+                if (other.title != null)
+                    return false;
+            } else if (!title.equals(other.title))
+                return false;
+            if (visibTitle != other.visibTitle)
+                return false;
+            if (visibWhen != other.visibWhen)
+                return false;
+            if (visibWhere != other.visibWhere)
+                return false;
+            if (when == null) {
+                if (other.when != null)
+                    return false;
+            } else if (!when.equals(other.when))
+                return false;
+            if (where == null) {
+                if (other.where != null)
+                    return false;
+            } else if (!where.equals(other.where))
+                return false;
+            return true;
+        }
+    }
+
+    /**
+     * {@link DayInfo} is a class that represents a day header in the widget. It
+     * contains all of the data necessary to display that day header, including
+     * the properly localized string.
+     */
+    static class DayInfo {
+
+        /** The Julian day */
+        final int mJulianDay;
+
+        /** The string representation of this day header, to be displayed */
+        final String mDayLabel;
+
+        DayInfo(int julianDay, String label) {
+            mJulianDay = julianDay;
+            mDayLabel = label;
+        }
+
+        @Override
+        public String toString() {
+            return mDayLabel;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((mDayLabel == null) ? 0 : mDayLabel.hashCode());
+            result = prime * result + mJulianDay;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            DayInfo other = (DayInfo) obj;
+            if (mDayLabel == null) {
+                if (other.mDayLabel != null)
+                    return false;
+            } else if (!mDayLabel.equals(other.mDayLabel))
+                return false;
+            if (mJulianDay != other.mJulianDay)
+                return false;
+            return true;
+        }
+
+    }
+
+    String mDayOfWeek;
+
+    String mDayOfMonth;
+
+    final List<RowInfo> mRowInfos;
+
+    final List<EventInfo> mEventInfos;
+
+    final List<DayInfo> mDayInfos;
+
+    final Context mContext;
+
+    final long mNow;
+
+    final long mStartOfNextDay;
+
+    final int mTodayJulianDay;
+
+    final int mMaxJulianDay;
+
+    public CalendarAppWidgetModel(Context context) {
+        mNow = System.currentTimeMillis();
+        Time time = new Time();
+        time.set(mNow);
+        time.monthDay++;
+        time.hour = 0;
+        time.minute = 0;
+        time.second = 0;
+        mStartOfNextDay = time.normalize(true);
+
+        long localOffset = TimeZone.getDefault().getOffset(mNow) / 1000;
+        mTodayJulianDay = Time.getJulianDay(mNow, localOffset);
+        mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1;
+
+        // Calendar header
+        String dayOfWeek = DateUtils.getDayOfWeekString(
+                time.weekDay + 1, DateUtils.LENGTH_MEDIUM).toUpperCase();
+
+        mDayOfWeek = dayOfWeek;
+        mDayOfMonth = Integer.toString(time.monthDay);
+
+        mEventInfos = new ArrayList<EventInfo>(50);
+        mRowInfos = new ArrayList<RowInfo>(50);
+        mDayInfos = new ArrayList<DayInfo>(8);
+        mContext = context;
+    }
+
+    public void buildFromCursor(Cursor cursor, String timeZone) {
+        Time recycle = new Time(timeZone);
+        final ArrayList<LinkedList<RowInfo>> mBuckets =
+            new ArrayList<LinkedList<RowInfo>>(CalendarAppWidgetService.MAX_DAYS);
+        for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) {
+            mBuckets.add(new LinkedList<RowInfo>());
+        }
+        recycle.setToNow();
+        mShowTZ = !TextUtils.equals(timeZone, Time.getCurrentTimezone());
+        if (mShowTZ) {
+            mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(recycle.isDst != 0,
+                    TimeZone.SHORT);
+        }
+
+        cursor.moveToPosition(-1);
+        while (cursor.moveToNext()) {
+            int rowId = cursor.getPosition();
+            long eventId = cursor.getLong(CalendarAppWidgetService.INDEX_EVENT_ID);
+            boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0;
+            long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN);
+            long end = cursor.getLong(CalendarAppWidgetService.INDEX_END);
+            String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE);
+            String location = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION);
+
+            // we don't compute these ourselves because it seems to produce the
+            // wrong endDay for all day events
+            int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY);
+            int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY);
+
+            // Adjust all-day times into local timezone
+            if (allDay) {
+                start = Utils.convertUtcToLocal(recycle, start);
+                end = Utils.convertUtcToLocal(recycle, end);
+            }
+
+            if (LOGD) {
+                Log.d(TAG, "Row #" + rowId + " allDay:" + allDay + " start:" + start
+                        + " end:" + end + " eventId:" + eventId);
+            }
+
+            // we might get some extra events when querying, in order to
+            // deal with all-day events
+            if (end < mNow) {
+                continue;
+            }
+
+            // Skip events that have already passed their flip times
+            long eventFlip = CalendarAppWidgetService.getEventFlip(start, end, allDay);
+            if (LOGD) Log.d(TAG, "Calculated flip time "
+                    + CalendarAppWidgetService.formatDebugTime(eventFlip, mNow));
+            if (eventFlip < mNow) {
+                continue;
+            }
+
+            int i = mEventInfos.size();
+            mEventInfos.add(populateEventInfo(
+                    allDay, start, end, startDay, endDay, title, location));
+            // populate the day buckets that this event falls into
+            int from = Math.max(startDay, mTodayJulianDay);
+            int to = Math.min(endDay, mMaxJulianDay);
+            for (int day = from; day <= to; day++) {
+                LinkedList<RowInfo> bucket = mBuckets.get(day - mTodayJulianDay);
+                RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i);
+                if (allDay) {
+                    bucket.addFirst(rowInfo);
+                } else {
+                    bucket.add(rowInfo);
+                }
+            }
+        }
+
+        int day = mTodayJulianDay;
+        int count = 0;
+        for (LinkedList<RowInfo> bucket : mBuckets) {
+            if (!bucket.isEmpty()) {
+                DayInfo dayInfo = populateDayInfo(day, recycle);
+                // Add the day header
+                int dayIndex = mDayInfos.size();
+                mDayInfos.add(dayInfo);
+                mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex));
+                // Add the event row infos
+                mRowInfos.addAll(bucket);
+                count += bucket.size();
+            }
+            day++;
+            if (count >= CalendarAppWidgetService.EVENT_MIN_COUNT) {
+                break;
+            }
+        }
+    }
+
+    private EventInfo populateEventInfo(boolean allDay, long start, long end,
+            int startDay, int endDay, String title, String location) {
+        EventInfo eventInfo = new EventInfo();
+
+        boolean eventIsInProgress = start <= mNow && end > mNow;
+
+        // Compute a human-readable string for the start time of the event
+        StringBuilder whenString = new StringBuilder();
+        int visibWhen;
+        if (allDay) {
+            whenString.setLength(0);
+            visibWhen = View.GONE;
+        } else {
+            int flags = DateUtils.FORMAT_ABBREV_ALL;
+            flags |= DateUtils.FORMAT_SHOW_TIME;
+            if (DateFormat.is24HourFormat(mContext)) {
+                flags |= DateUtils.FORMAT_24HOUR;
+            }
+            if (endDay > startDay) {
+                flags |= DateUtils.FORMAT_SHOW_DATE;
+                whenString.append(Utils.formatDateRange(mContext, start, end, flags));
+            } else {
+                whenString.append(Utils.formatDateRange(mContext, start, start, flags));
+            }
+            String tz = Utils.getTimeZone(mContext, null);
+            if (mShowTZ) {
+                whenString.append(" ").append(mHomeTZName);
+            }
+            // TODO better i18n formatting
+            if (eventIsInProgress) {
+                whenString.append(" (").append(mContext.getString(R.string.in_progress))
+                        .append(")");
+            }
+            visibWhen = View.VISIBLE;
+        }
+        eventInfo.start = start;
+        eventInfo.end = end;
+        eventInfo.allDay = allDay;
+        eventInfo.when = whenString.toString();
+        eventInfo.visibWhen = visibWhen;
+
+        // What
+        if (TextUtils.isEmpty(title)) {
+            eventInfo.title = mContext.getString(R.string.no_title_label);
+        } else {
+            eventInfo.title = title;
+        }
+        eventInfo.visibTitle = View.VISIBLE;
+
+        // Where
+        if (!TextUtils.isEmpty(location)) {
+            eventInfo.visibWhere = View.VISIBLE;
+            eventInfo.where = location;
+        } else {
+            eventInfo.visibWhere = View.GONE;
+        }
+        return eventInfo;
+    }
+
+    private DayInfo populateDayInfo(int julianDay, Time recycle) {
+        long millis = recycle.setJulianDay(julianDay);
+        int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC;
+        flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
+
+        String label;
+        if (julianDay == mTodayJulianDay) {
+            label = mContext.getString(R.string.today);
+            return new DayInfo(julianDay, label);
+        }
+
+        if (julianDay == mTodayJulianDay + 1) {
+            label = mContext.getString(R.string.tomorrow);
+        } else {
+            label = DateUtils.formatDateRange(mContext, millis, millis, flags);
+        }
+
+        flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_UTC;
+        flags |= DateUtils.FORMAT_SHOW_DATE;
+
+        label += ", ";
+        label += DateUtils.formatDateRange(mContext, millis, millis, flags);
+
+        return new DayInfo(julianDay, label);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("\nCalendarAppWidgetModel [eventInfos=");
+        builder.append(mEventInfos);
+        builder.append(", dayOfMonth=");
+        builder.append(mDayOfMonth);
+        builder.append(", dayOfWeek=");
+        builder.append(mDayOfWeek);
+        builder.append("]");
+        return builder.toString();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetProvider.java b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
new file mode 100644
index 0000000..821ff83
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetProvider.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2009 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.calendar.widget;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+/**
+ * Simple widget to show next upcoming calendar event.
+ */
+public class CalendarAppWidgetProvider extends AppWidgetProvider {
+    static final String TAG = "CalendarAppWidgetProvider";
+    static final boolean LOGD = false;
+
+    static final String ACTION_CALENDAR_APPWIDGET_UPDATE =
+            "com.android.calendar.APPWIDGET_UPDATE";
+
+    // TODO Move these to Calendar.java
+    static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Handle calendar-specific updates ourselves because they might be
+        // coming in without extras, which AppWidgetProvider then blocks.
+        final String action = intent.getAction();
+        if (ACTION_CALENDAR_APPWIDGET_UPDATE.equals(action)) {
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+            performUpdate(context, appWidgetManager,
+                    appWidgetManager.getAppWidgetIds(getComponentName(context)),
+                    null /* no eventIds */);
+        } else {
+            super.onReceive(context, intent);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onEnabled(Context context) {
+        // Enable updates for timezone, date, and provider changes
+        PackageManager pm = context.getPackageManager();
+        pm.setComponentEnabledSetting(
+                new ComponentName(context, CalendarAppWidgetReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDisabled(Context context) {
+        // Unsubscribe from all AlarmManager updates
+        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        PendingIntent pendingUpdate = getUpdateIntent(context);
+        am.cancel(pendingUpdate);
+
+        // Disable updates for timezone, date, and provider changes
+        PackageManager pm = context.getPackageManager();
+        pm.setComponentEnabledSetting(
+                new ComponentName(context, CalendarAppWidgetReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        performUpdate(context, appWidgetManager, appWidgetIds, null /* no eventIds */);
+    }
+
+
+    /**
+     * Build {@link ComponentName} describing this specific
+     * {@link AppWidgetProvider}
+     */
+    static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, CalendarAppWidgetProvider.class);
+    }
+
+    /**
+     * Process and push out an update for the given appWidgetIds. This call
+     * actually fires an intent to start {@link CalendarAppWidgetService} as a
+     * background service which handles the actual update, to prevent ANR'ing
+     * during database queries.
+     *
+     * @param context Context to use when starting {@link CalendarAppWidgetService}.
+     * @param appWidgetIds List of specific appWidgetIds to update, or null for
+     *            all.
+     * @param changedEventIds Specific events known to be changed. If present,
+     *            we use it to decide if an update is necessary.
+     */
+    private void performUpdate(Context context,
+            AppWidgetManager appWidgetManager, int[] appWidgetIds,
+            long[] changedEventIds) {
+        // Launch over to service so it can perform update
+        for (int appWidgetId : appWidgetIds) {
+            if (LOGD) Log.d(TAG, "Building widget update...");
+            Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
+            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+            if (changedEventIds != null) {
+                updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
+            }
+            updateIntent.setData(Uri.parse(updateIntent.toUri(Intent.URI_INTENT_SCHEME)));
+
+            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
+            // Calendar header
+            Time time = new Time();
+            time.setToNow();
+            String dayOfWeek = DateUtils.getDayOfWeekString(
+                    time.weekDay + 1, DateUtils.LENGTH_MEDIUM).toUpperCase();
+            views.setTextViewText(R.id.day_of_week, dayOfWeek);
+            views.setTextViewText(R.id.day_of_month, Integer.toString(time.monthDay));
+            // Attach to list of events
+            views.setRemoteAdapter(R.id.events_list, updateIntent);
+            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.events_list);
+
+            // Clicking on the widget launches Calendar
+            // TODO fix this exact behavior?
+            // long startTime = Math.max(currentTime, events.firstTime);
+            long startTime = System.currentTimeMillis();
+
+            // Each list item will call setOnClickExtra() to let the list know which item
+            // is selected by a user.
+            final PendingIntent updateEventIntent = getLaunchPendingIntentTemplate(context);
+            views.setPendingIntentTemplate(R.id.events_list, updateEventIntent);
+
+            // ImageButton is not a collection so we cannot/shouldn't call
+            // setPendingIntentTemplate().
+            final PendingIntent newEventIntent = getNewEventPendingIntent(context);
+            views.setOnClickPendingIntent(R.id.new_event_button, newEventIntent);
+
+            appWidgetManager.updateAppWidget(appWidgetId, views);
+        }
+    }
+
+    /**
+     * Build the {@link PendingIntent} used to trigger an update of all calendar
+     * widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target
+     * all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
+     *
+     * @param context Context to use when building broadcast.
+     */
+    static PendingIntent getUpdateIntent(Context context) {
+        Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE);
+        updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class));
+        return PendingIntent.getBroadcast(context, 0 /* no requestCode */,
+                updateIntent, 0 /* no flags */);
+    }
+
+    /**
+     * Build a {@link PendingIntent} to launch the Calendar app. This should be used
+     * in combination with {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)}.
+     */
+    static PendingIntent getLaunchPendingIntentTemplate(Context context) {
+        Intent launchIntent = new Intent();
+        launchIntent.setAction(Intent.ACTION_VIEW);
+        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
+                Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return PendingIntent.getActivity(context, 0 /* no requestCode */,
+                launchIntent,  PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+    /**
+     * Build an {@link Intent} available as FillInIntent to launch the Calendar app.
+     * This should be used in combination with
+     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
+     * If the go to time is 0, then calendar will be launched without a starting time.
+     *
+     * @param goToTime time that calendar should take the user to, or 0 to
+     *            indicate no specific start time.
+     */
+    static Intent getLaunchFillInIntent(long goToTime) {
+        final Intent fillInIntent = new Intent();
+        String dataString = "content://com.android.calendar/time";
+        if (goToTime != 0) {
+            fillInIntent.putExtra(Utils.INTENT_KEY_DETAIL_VIEW, true);
+            dataString += "/" + goToTime;
+        }
+        Uri data = Uri.parse(dataString);
+        fillInIntent.setData(data);
+        return fillInIntent;
+    }
+
+    private static PendingIntent getNewEventPendingIntent(Context context) {
+        Intent newEventIntent = new Intent(Intent.ACTION_EDIT);
+        newEventIntent.setType("vnd.android.cursor.item/event");
+        return PendingIntent.getActivity(context, 0, newEventIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetReceiver.java b/src/com/android/calendar/widget/CalendarAppWidgetReceiver.java
new file mode 100644
index 0000000..cfb83aa
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetReceiver.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 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.calendar.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class CalendarAppWidgetReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "CalendarAppWidgetReceiver";
+    private static final boolean LOGD = false;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Launch over to service so it can perform update
+        final Intent updateIntent = new Intent(
+                CalendarAppWidgetProvider.ACTION_CALENDAR_APPWIDGET_UPDATE);
+
+        // Copy over the relevant extra fields if they exist
+        if (intent.hasExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS)) {
+            int[] data = intent.getIntArrayExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS);
+            updateIntent.putExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS, data);
+        }
+
+        if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+            int data = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, data);
+        }
+
+        if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS)) {
+            int[] data = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+            updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, data);
+        }
+
+        if (LOGD) Log.d(TAG, "Something changed, updating widget");
+        context.sendBroadcast(updateIntent);
+    }
+}
diff --git a/src/com/android/calendar/widget/CalendarAppWidgetService.java b/src/com/android/calendar/widget/CalendarAppWidgetService.java
new file mode 100644
index 0000000..d3f4e66
--- /dev/null
+++ b/src/com/android/calendar/widget/CalendarAppWidgetService.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2009 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.calendar.widget;
+
+import com.android.calendar.R;
+import com.android.calendar.Utils;
+import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo;
+import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
+import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo;
+import com.google.common.annotations.VisibleForTesting;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.CalendarCache;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Instances;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+
+public class CalendarAppWidgetService extends RemoteViewsService {
+    private static final String TAG = "CalendarAppWidgetService";
+    private static final boolean LOGD = false;
+
+    static final int EVENT_MIN_COUNT = 20;
+
+    static final int EVENT_MAX_COUNT = 503;
+
+    private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
+            + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
+            + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT;
+
+    // TODO can't use parameter here because provider is dropping them
+    private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND "
+            + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
+
+    static final String[] EVENT_PROJECTION = new String[] {
+        Instances.ALL_DAY,
+        Instances.BEGIN,
+        Instances.END,
+        Instances.TITLE,
+        Instances.EVENT_LOCATION,
+        Instances.EVENT_ID,
+        Instances.START_DAY,
+        Instances.END_DAY
+    };
+
+    static final int INDEX_ALL_DAY = 0;
+    static final int INDEX_BEGIN = 1;
+    static final int INDEX_END = 2;
+    static final int INDEX_TITLE = 3;
+    static final int INDEX_EVENT_LOCATION = 4;
+    static final int INDEX_EVENT_ID = 5;
+    static final int INDEX_START_DAY = 6;
+    static final int INDEX_END_DAY = 7;
+
+    static final int MAX_DAYS = 7;
+
+    private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS;
+
+    // If no next-update calculated, or bad trigger time in past, schedule
+    // update about six hours from now.
+    private static final long UPDATE_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        return new CalendarFactory(getApplicationContext(), intent);
+    }
+
+    protected static class CalendarFactory implements RemoteViewsService.RemoteViewsFactory {
+
+        private static final String TAG = CalendarFactory.class.getSimpleName();
+
+        private static final boolean LOGD = false;
+
+        private final int mAppWidgetId;
+
+        private Context mContext;
+
+        private CalendarAppWidgetModel mModel;
+
+        private Cursor mCursor;
+
+        protected CalendarFactory(Context context, Intent intent) {
+            mContext = context;
+            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
+        }
+
+        @Override
+        public void onCreate() {
+            loadData();
+        }
+
+        @Override
+        public void onDataSetChanged() {
+            loadData();
+        }
+
+        @Override
+        public void onDestroy() {
+            mCursor.close();
+        }
+
+        @Override
+        public RemoteViews getLoadingView() {
+            RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                    R.layout.appwidget_loading);
+            return views;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int position) {
+            // we use getCount here so that it doesn't return null when empty
+            if (position < 0 || position >= getCount()) {
+                return null;
+            }
+
+            if (mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) {
+                RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                        R.layout.appwidget_no_events);
+                final Intent intent =  CalendarAppWidgetProvider.getLaunchFillInIntent(0);
+                views.setOnClickFillInIntent(R.id.appwidget_no_events, intent);
+                return views;
+            }
+
+            RowInfo rowInfo = mModel.mRowInfos.get(position);
+            if (rowInfo.mType == RowInfo.TYPE_DAY) {
+                RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                        R.layout.appwidget_day);
+                DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex);
+                updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel);
+                return views;
+            } else {
+                RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                        R.layout.appwidget_row);
+
+                EventInfo e = mModel.mEventInfos.get(rowInfo.mIndex);
+
+                updateTextView(views, R.id.when, e.visibWhen, e.when);
+                updateTextView(views, R.id.where, e.visibWhere, e.where);
+                updateTextView(views, R.id.title, e.visibTitle, e.title);
+
+                // An element in ListView.
+                final Intent fillInIntent =
+                        CalendarAppWidgetProvider.getLaunchFillInIntent(e.start);
+                views.setOnClickFillInIntent(R.id.appwidget_row, fillInIntent);
+                return views;
+            }
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 4;
+        }
+
+        @Override
+        public int getCount() {
+            // if there are no events, we still return 1 to represent the "no
+            // events" view
+            return Math.max(1, mModel.mRowInfos.size());
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        private void loadData() {
+            long now = System.currentTimeMillis();
+            if (LOGD) Log.d(TAG, "Querying for widget events...");
+            if (mCursor != null) {
+                mCursor.close();
+            }
+
+            ContentResolver cr = mContext.getContentResolver();
+            mCursor = getUpcomingInstancesCursor(cr, SEARCH_DURATION, now);
+            String tz = getTimeZoneFromDB(cr);
+            mModel = buildAppWidgetModel(mContext, mCursor, tz);
+            long triggerTime = calculateUpdateTime(mModel);
+            // Schedule an alarm to wake ourselves up for the next update.  We also cancel
+            // all existing wake-ups because PendingIntents don't match against extras.
+
+            // If no next-update calculated, or bad trigger time in past, schedule
+            // update about six hours from now.
+            if (triggerTime == -1 || triggerTime < now) {
+                if (LOGD) Log.w(TAG, "Encountered bad trigger time " +
+                        formatDebugTime(triggerTime, now));
+                triggerTime = now + UPDATE_NO_EVENTS;
+            }
+
+            AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+            PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(mContext);
+
+            am.cancel(pendingUpdate);
+            am.set(AlarmManager.RTC, triggerTime, pendingUpdate);
+            if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now));
+        }
+
+        /**
+         * Query across all calendars for upcoming event instances from now until
+         * some time in the future.
+         *
+         * Widen the time range that we query by one day on each end so that we can
+         * catch all-day events. All-day events are stored starting at midnight in
+         * UTC but should be included in the list of events starting at midnight
+         * local time. This may fetch more events than we actually want, so we
+         * filter them out later.
+         *
+         * @param resolver {@link ContentResolver} to use when querying
+         *            {@link Instances#CONTENT_URI}.
+         * @param searchDuration Distance into the future to look for event
+         *            instances, in milliseconds.
+         * @param now Current system time to use for this update, possibly from
+         *            {@link System#currentTimeMillis()}.
+         */
+        private Cursor getUpcomingInstancesCursor(ContentResolver resolver,
+                long searchDuration, long now) {
+            // Search for events from now until some time in the future
+
+            // Add a day on either side to catch all-day events
+            long begin = now - DateUtils.DAY_IN_MILLIS;
+            long end = now + searchDuration + DateUtils.DAY_IN_MILLIS;
+
+            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI,
+                    String.format("%d/%d", begin, end));
+
+            Cursor cursor = resolver.query(uri, EVENT_PROJECTION,
+                    EVENT_SELECTION, null, EVENT_SORT_ORDER);
+
+            // Start managing the cursor ourselves
+            MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor);
+            cursor.close();
+
+            return matrixCursor;
+        }
+
+        private String getTimeZoneFromDB(ContentResolver resolver) {
+            String tz = null;
+            Cursor tzCursor = null;
+            try {
+                tzCursor = resolver.query(
+                        CalendarCache.URI, CalendarCache.POJECTION, null, null, null);
+                if (tzCursor != null) {
+                    int keyColumn = tzCursor.getColumnIndexOrThrow(CalendarCache.KEY);
+                    int valueColumn = tzCursor.getColumnIndexOrThrow(CalendarCache.VALUE);
+                    while (tzCursor.moveToNext()) {
+                        if (TextUtils.equals(tzCursor.getString(keyColumn),
+                                CalendarCache.TIMEZONE_KEY_INSTANCES)) {
+                            tz = tzCursor.getString(valueColumn);
+                        }
+                    }
+                }
+                if (tz == null) {
+                    tz = Time.getCurrentTimezone();
+                }
+            } finally {
+                if (tzCursor != null) {
+                    tzCursor.close();
+                }
+            }
+            return tz;
+        }
+
+        @VisibleForTesting
+        protected static CalendarAppWidgetModel buildAppWidgetModel(
+                Context context, Cursor cursor, String timeZone) {
+            CalendarAppWidgetModel model = new CalendarAppWidgetModel(context);
+            model.buildFromCursor(cursor, timeZone);
+            return model;
+        }
+
+        /**
+         * Figure out the next time we should push widget updates, usually the time
+         * calculated by {@link #getEventFlip(Cursor, long, long, boolean)}.
+         *
+         * @param cursor Valid cursor on {@link Instances#CONTENT_URI}
+         * @param events {@link MarkedEvents} parsed from the cursor
+         */
+        private long calculateUpdateTime(CalendarAppWidgetModel model) {
+            long result = -1;
+            if (!model.mEventInfos.isEmpty()) {
+                EventInfo firstEvent = model.mEventInfos.get(0);
+                long start = firstEvent.start;
+                long end = firstEvent.end;
+                boolean allDay = firstEvent.allDay;
+
+                // Adjust all-day times into local timezone
+                if (allDay) {
+                    final Time recycle = new Time();
+                    start = Utils.convertUtcToLocal(recycle, start);
+                    end = Utils.convertUtcToLocal(recycle, end);
+                }
+
+                result = getEventFlip(start, end, allDay);
+
+                // Make sure an update happens at midnight or earlier
+                long midnight = getNextMidnightTimeMillis();
+                result = Math.min(midnight, result);
+            }
+            return result;
+        }
+
+        private static long getNextMidnightTimeMillis() {
+            Time time = new Time();
+            time.setToNow();
+            time.monthDay++;
+            time.hour = 0;
+            time.minute = 0;
+            time.second = 0;
+            long midnight = time.normalize(true);
+            return midnight;
+        }
+
+        static void updateTextView(RemoteViews views, int id, int visibility, String string) {
+            views.setViewVisibility(id, visibility);
+            if (visibility == View.VISIBLE) {
+                views.setTextViewText(id, string);
+            }
+        }
+    }
+
+    /**
+     * Format given time for debugging output.
+     *
+     * @param unixTime Target time to report.
+     * @param now Current system time from {@link System#currentTimeMillis()}
+     *            for calculating time difference.
+     */
+    static String formatDebugTime(long unixTime, long now) {
+        Time time = new Time();
+        time.set(unixTime);
+
+        long delta = unixTime - now;
+        if (delta > DateUtils.MINUTE_IN_MILLIS) {
+            delta /= DateUtils.MINUTE_IN_MILLIS;
+            return String.format("[%d] %s (%+d mins)", unixTime,
+                    time.format("%H:%M:%S"), delta);
+        } else {
+            delta /= DateUtils.SECOND_IN_MILLIS;
+            return String.format("[%d] %s (%+d secs)", unixTime,
+                    time.format("%H:%M:%S"), delta);
+        }
+    }
+
+    /**
+     * Calculate flipping point for the given event; when we should hide this
+     * event and show the next one. This is defined as the end time of the
+     * event.
+     *
+     * @param start Event start time in local timezone.
+     * @param end Event end time in local timezone.
+     * @param allDay whether or not the event is all-day
+     */
+    static long getEventFlip(long start, long end, boolean allDay) {
+        return end;
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 28a573f..f71150c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,6 +1,8 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_EMMA_COVERAGE_FILTER := +com.android.calendar.*
+
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
 
diff --git a/tests/src/com/android/calendar/AsyncQueryServiceTest.java b/tests/src/com/android/calendar/AsyncQueryServiceTest.java
new file mode 100644
index 0000000..c9f927c
--- /dev/null
+++ b/tests/src/com/android/calendar/AsyncQueryServiceTest.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2007 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.calendar;
+
+import com.android.calendar.AsyncQueryService.Operation;
+import com.android.calendar.AsyncQueryServiceHelper.OperationInfo;
+
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.test.ServiceTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.mock.MockCursor;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Smoke;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link android.text.format.DateUtils#formatDateRange}.
+ */
+public class AsyncQueryServiceTest extends ServiceTestCase<AsyncQueryServiceHelper> {
+    private static final String TAG = "AsyncQueryServiceTest";
+
+    private static final String AUTHORITY_URI = "content://AsyncQueryAuthority/";
+
+    private static final String AUTHORITY = "AsyncQueryAuthority";
+
+    private static final int MIN_DELAY = 50;
+
+    private static final int BASE_TEST_WAIT_TIME = MIN_DELAY * 5;
+
+    private static int mId = 0;
+
+    private static final String[] TEST_PROJECTION = new String[] {
+            "col1", "col2", "col3"
+    };
+
+    private static final String TEST_SELECTION = "selection";
+
+    private static final String[] TEST_SELECTION_ARGS = new String[] {
+            "arg1", "arg2", "arg3"
+    };
+
+    public AsyncQueryServiceTest() {
+        super(AsyncQueryServiceHelper.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Smoke
+    @SmallTest
+    public void testQuery() throws Exception {
+        int index = 0;
+        final OperationInfo[] work = new OperationInfo[1];
+        work[index] = new OperationInfo();
+        work[index].op = Operation.EVENT_ARG_QUERY;
+
+        work[index].token = ++mId;
+        work[index].cookie = ++mId;
+        work[index].uri = Uri.parse(AUTHORITY_URI + "blah");
+        work[index].projection = TEST_PROJECTION;
+        work[index].selection = TEST_SELECTION;
+        work[index].selectionArgs = TEST_SELECTION_ARGS;
+        work[index].orderBy = "order";
+
+        work[index].delayMillis = 0;
+        work[index].result = new TestCursor();
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work);
+        aqs.startQuery(work[index].token, work[index].cookie, work[index].uri,
+                work[index].projection, work[index].selection, work[index].selectionArgs,
+                work[index].orderBy);
+
+        Log.d(TAG, "testQuery Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testQuery Done <<<<<<<<<<<<<<");
+    }
+
+    @SmallTest
+    public void testInsert() throws Exception {
+        int index = 0;
+        final OperationInfo[] work = new OperationInfo[1];
+        work[index] = new OperationInfo();
+        work[index].op = Operation.EVENT_ARG_INSERT;
+
+        work[index].token = ++mId;
+        work[index].cookie = ++mId;
+        work[index].uri = Uri.parse(AUTHORITY_URI + "blah");
+        work[index].values = new ContentValues();
+        work[index].values.put("key", ++mId);
+
+        work[index].delayMillis = 0;
+        work[index].result = Uri.parse(AUTHORITY_URI + "Result=" + ++mId);
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work);
+        aqs.startInsert(work[index].token, work[index].cookie, work[index].uri, work[index].values,
+                work[index].delayMillis);
+
+        Log.d(TAG, "testInsert Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testInsert Done <<<<<<<<<<<<<<");
+    }
+
+    @SmallTest
+    public void testUpdate() throws Exception {
+        int index = 0;
+        final OperationInfo[] work = new OperationInfo[1];
+        work[index] = new OperationInfo();
+        work[index].op = Operation.EVENT_ARG_UPDATE;
+
+        work[index].token = ++mId;
+        work[index].cookie = ++mId;
+        work[index].uri = Uri.parse(AUTHORITY_URI + ++mId);
+        work[index].values = new ContentValues();
+        work[index].values.put("key", ++mId);
+        work[index].selection = TEST_SELECTION;
+        work[index].selectionArgs = TEST_SELECTION_ARGS;
+
+        work[index].delayMillis = 0;
+        work[index].result = ++mId;
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work);
+        aqs.startUpdate(work[index].token, work[index].cookie, work[index].uri, work[index].values,
+                work[index].selection, work[index].selectionArgs, work[index].delayMillis);
+
+        Log.d(TAG, "testUpdate Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testUpdate Done <<<<<<<<<<<<<<");
+    }
+
+    @SmallTest
+    public void testDelete() throws Exception {
+        int index = 0;
+        final OperationInfo[] work = new OperationInfo[1];
+        work[index] = new OperationInfo();
+        work[index].op = Operation.EVENT_ARG_DELETE;
+
+        work[index].token = ++mId;
+        work[index].cookie = ++mId;
+        work[index].uri = Uri.parse(AUTHORITY_URI + "blah");
+        work[index].selection = TEST_SELECTION;
+        work[index].selectionArgs = TEST_SELECTION_ARGS;
+
+        work[index].delayMillis = 0;
+        work[index].result = ++mId;
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work);
+        aqs.startDelete(work[index].token,
+                work[index].cookie,
+                work[index].uri,
+                work[index].selection,
+                work[index].selectionArgs,
+                work[index].delayMillis);
+
+        Log.d(TAG, "testDelete Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testDelete Done <<<<<<<<<<<<<<");
+    }
+
+    @SmallTest
+    public void testBatch() throws Exception {
+        int index = 0;
+        final OperationInfo[] work = new OperationInfo[1];
+        work[index] = new OperationInfo();
+        work[index].op = Operation.EVENT_ARG_BATCH;
+
+        work[index].token = ++mId;
+        work[index].cookie = ++mId;
+        work[index].authority = AUTHORITY;
+        work[index].cpo = new ArrayList<ContentProviderOperation>();
+        work[index].cpo.add(ContentProviderOperation.newInsert(Uri.parse(AUTHORITY_URI + ++mId))
+                .build());
+
+        work[index].delayMillis = 0;
+        ContentProviderResult[] resultArray = new ContentProviderResult[1];
+        resultArray[0] = new ContentProviderResult(++mId);
+        work[index].result = resultArray;
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(work), work);
+        aqs.startBatch(work[index].token,
+                work[index].cookie,
+                work[index].authority,
+                work[index].cpo,
+                work[index].delayMillis);
+
+        Log.d(TAG, "testBatch Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testBatch Done <<<<<<<<<<<<<<");
+    }
+
+    @LargeTest
+    public void testDelay() throws Exception {
+        // Tests the ordering of the workqueue
+        int index = 0;
+        OperationInfo[] work = new OperationInfo[5];
+        work[index++] = generateWork(MIN_DELAY * 2);
+        work[index++] = generateWork(0);
+        work[index++] = generateWork(MIN_DELAY * 1);
+        work[index++] = generateWork(0);
+        work[index++] = generateWork(MIN_DELAY * 3);
+
+        OperationInfo[] sorted = generateSortedWork(work, work.length);
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(sorted), sorted);
+        startWork(aqs, work);
+
+        Log.d(TAG, "testDelay Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", work.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testDelay Done <<<<<<<<<<<<<<");
+    }
+
+    @LargeTest
+    public void testCancel_simpleCancelLastTest() throws Exception {
+        int index = 0;
+        OperationInfo[] work = new OperationInfo[5];
+        work[index++] = generateWork(MIN_DELAY * 2);
+        work[index++] = generateWork(0);
+        work[index++] = generateWork(MIN_DELAY);
+        work[index++] = generateWork(0);
+        work[index] = generateWork(MIN_DELAY * 3);
+
+        // Not part of the expected as it will be canceled
+        OperationInfo toBeCancelled1 = work[index];
+        OperationInfo[] expected = generateSortedWork(work, work.length - 1);
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected);
+        startWork(aqs, work);
+        Operation lastOne = aqs.getLastCancelableOperation();
+        // Log.d(TAG, "lastOne = " + lastOne.toString());
+        // Log.d(TAG, "toBeCancelled1 = " + toBeCancelled1.toString());
+        assertTrue("1) delay=3 is not last", toBeCancelled1.equivalent(lastOne));
+        assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(lastOne.token));
+
+        Log.d(TAG, "testCancel_simpleCancelLastTest Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", expected.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testCancel_simpleCancelLastTest Done <<<<<<<<<<<<<<");
+    }
+
+    @LargeTest
+    public void testCancel_cancelSecondToLast() throws Exception {
+        int index = 0;
+        OperationInfo[] work = new OperationInfo[5];
+        work[index++] = generateWork(MIN_DELAY * 2);
+        work[index++] = generateWork(0);
+        work[index++] = generateWork(MIN_DELAY);
+        work[index++] = generateWork(0);
+        work[index] = generateWork(MIN_DELAY * 3);
+
+        // Not part of the expected as it will be canceled
+        OperationInfo toBeCancelled1 = work[index];
+        OperationInfo[] expected = new OperationInfo[4];
+        expected[0] = work[1]; // delay = 0
+        expected[1] = work[3]; // delay = 0
+        expected[2] = work[2]; // delay = MIN_DELAY
+        expected[3] = work[4]; // delay = MIN_DELAY * 3
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected);
+        startWork(aqs, work);
+
+        Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3
+        assertTrue("2) delay=3 is not last", toBeCancelled1.equivalent(lastOne));
+        assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token));
+        assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token));
+
+        Log.d(TAG, "testCancel_cancelSecondToLast Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", expected.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testCancel_cancelSecondToLast Done <<<<<<<<<<<<<<");
+    }
+
+    @LargeTest
+    public void testCancel_multipleCancels() throws Exception {
+        int index = 0;
+        OperationInfo[] work = new OperationInfo[5];
+        work[index++] = generateWork(MIN_DELAY * 2);
+        work[index++] = generateWork(0);
+        work[index++] = generateWork(MIN_DELAY);
+        work[index++] = generateWork(0);
+        work[index] = generateWork(MIN_DELAY * 3);
+
+        // Not part of the expected as it will be canceled
+        OperationInfo[] expected = new OperationInfo[3];
+        expected[0] = work[1]; // delay = 0
+        expected[1] = work[3]; // delay = 0
+        expected[2] = work[2]; // delay = MIN_DELAY
+
+        TestAsyncQueryService aqs = new TestAsyncQueryService(buildTestContext(expected), expected);
+        startWork(aqs, work);
+
+        Operation lastOne = aqs.getLastCancelableOperation(); // delay = 3
+        assertTrue("3) delay=3 is not last", work[4].equivalent(lastOne));
+        assertEquals("Can't cancel delay 2", 1, aqs.cancelOperation(work[0].token));
+        assertEquals("Delay 2 should be gone", 0, aqs.cancelOperation(work[0].token));
+        assertEquals("Can't cancel delay 3", 1, aqs.cancelOperation(work[4].token));
+        assertEquals("Delay 3 should be gone", 0, aqs.cancelOperation(work[4].token));
+
+        Log.d(TAG, "testCancel_multipleCancels Waiting >>>>>>>>>>>");
+        assertEquals("Not all operations were executed.", expected.length, aqs
+                .waitForCompletion(BASE_TEST_WAIT_TIME));
+        Log.d(TAG, "testCancel_multipleCancels Done <<<<<<<<<<<<<<");
+    }
+
+    private OperationInfo generateWork(long delayMillis) {
+        OperationInfo work = new OperationInfo();
+        work.op = Operation.EVENT_ARG_DELETE;
+
+        work.token = ++mId;
+        work.cookie = 100 + work.token;
+        work.uri = Uri.parse(AUTHORITY_URI + "blah");
+        work.selection = TEST_SELECTION;
+        work.selectionArgs = TEST_SELECTION_ARGS;
+
+        work.delayMillis = delayMillis;
+        work.result = 1000 + work.token;
+        return work;
+    }
+
+    private void startWork(TestAsyncQueryService aqs, OperationInfo[] work) {
+        for (OperationInfo w : work) {
+            if (w != null) {
+                aqs.startDelete(w.token, w.cookie, w.uri, w.selection, w.selectionArgs,
+                        w.delayMillis);
+            }
+        }
+    }
+
+    OperationInfo[] generateSortedWork(OperationInfo[] work, int length) {
+        OperationInfo[] sorted = new OperationInfo[length];
+        System.arraycopy(work, 0, sorted, 0, length);
+
+        // Set the scheduled time so they get sorted properly
+        for (OperationInfo w : sorted) {
+            if (w != null) {
+                w.calculateScheduledTime();
+            }
+        }
+
+        // Stable sort by scheduled time
+        Arrays.sort(sorted);
+
+        Log.d(TAG, "Unsorted work: " + work.length);
+        for (OperationInfo w : work) {
+            if (w != null) {
+                Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis);
+            }
+        }
+        Log.d(TAG, "Sorted work: " + sorted.length);
+        for (OperationInfo w : sorted) {
+            if (w != null) {
+                Log.d(TAG, "Token#" + w.token + " delay=" + w.delayMillis);
+            }
+        }
+
+        return sorted;
+    }
+
+    private Context buildTestContext(final OperationInfo[] work) {
+        MockContext context = new MockContext() {
+            MockContentResolver mResolver;
+
+            @Override
+            public ContentResolver getContentResolver() {
+                if (mResolver == null) {
+                    ContentProvider provider = new TestProvider(work);
+                    mResolver = new MockContentResolver();
+                    mResolver.addProvider(AUTHORITY, provider);
+                }
+                return mResolver;
+            }
+
+            @Override
+            public String getPackageName() {
+                return AsyncQueryServiceTest.class.getPackage().getName();
+            }
+
+            public ComponentName startService(Intent service) {
+                AsyncQueryServiceTest.this.startService(service);
+                return service.getComponent();
+            }
+        };
+
+        return context;
+    }
+
+    private final class TestCursor extends MockCursor {
+        int mUnique = ++mId;
+
+        @Override
+        public int getCount() {
+            return mUnique;
+        }
+    }
+
+    /**
+     * TestAsyncQueryService takes the expected results in the constructor. They
+     * are used to verify the data passed to the callbacks.
+     */
+    class TestAsyncQueryService extends AsyncQueryService {
+        int mIndex = 0;
+
+        private OperationInfo[] mWork;
+
+        private Semaphore mCountingSemaphore;
+
+        public TestAsyncQueryService(Context context, OperationInfo[] work) {
+            super(context);
+            mCountingSemaphore = new Semaphore(0);
+
+            // run in a separate thread but call the same code
+            HandlerThread thread = new HandlerThread("TestAsyncQueryService");
+            thread.start();
+            super.setTestHandler(new Handler(thread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    TestAsyncQueryService.this.handleMessage(msg);
+                }
+            });
+
+            mWork = work;
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            Log.d(TAG, "onQueryComplete tid=" + Thread.currentThread().getId());
+            Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex);
+
+            assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_QUERY);
+            assertEquals(mWork[mIndex].token, token);
+            /*
+             * Even though our TestProvider returned mWork[mIndex].result, it is
+             * wrapped with new'ed CursorWrapperInner and there's no equal() in
+             * CursorWrapperInner. assertEquals the two cursor will always fail.
+             * So just compare the count which will be unique in our TestCursor;
+             */
+            assertEquals(((Cursor) mWork[mIndex].result).getCount(), cursor.getCount());
+
+            mIndex++;
+            mCountingSemaphore.release();
+        }
+
+        @Override
+        protected void onInsertComplete(int token, Object cookie, Uri uri) {
+            Log.d(TAG, "onInsertComplete tid=" + Thread.currentThread().getId());
+            Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex);
+
+            assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_INSERT);
+            assertEquals(mWork[mIndex].token, token);
+            assertEquals(mWork[mIndex].result, uri);
+
+            mIndex++;
+            mCountingSemaphore.release();
+        }
+
+        @Override
+        protected void onUpdateComplete(int token, Object cookie, int result) {
+            Log.d(TAG, "onUpdateComplete tid=" + Thread.currentThread().getId());
+            Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex);
+
+            assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_UPDATE);
+            assertEquals(mWork[mIndex].token, token);
+            assertEquals(mWork[mIndex].result, result);
+
+            mIndex++;
+            mCountingSemaphore.release();
+        }
+
+        @Override
+        protected void onDeleteComplete(int token, Object cookie, int result) {
+            Log.d(TAG, "onDeleteComplete tid=" + Thread.currentThread().getId());
+            Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex);
+
+            assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_DELETE);
+            assertEquals(mWork[mIndex].token, token);
+            assertEquals(mWork[mIndex].result, result);
+
+            mIndex++;
+            mCountingSemaphore.release();
+        }
+
+        @Override
+        protected void onBatchComplete(int token, Object cookie, ContentProviderResult[] results) {
+            Log.d(TAG, "onBatchComplete tid=" + Thread.currentThread().getId());
+            Log.d(TAG, "mWork.length=" + mWork.length + " mIndex=" + mIndex);
+
+            assertEquals(mWork[mIndex].op, Operation.EVENT_ARG_BATCH);
+            assertEquals(mWork[mIndex].token, token);
+
+            ContentProviderResult[] expected = (ContentProviderResult[]) mWork[mIndex].result;
+            assertEquals(expected.length, results.length);
+            for (int i = 0; i < expected.length; ++i) {
+                assertEquals(expected[i].count, results[i].count);
+                assertEquals(expected[i].uri, results[i].uri);
+            }
+
+            mIndex++;
+            mCountingSemaphore.release();
+        }
+
+        public int waitForCompletion(long timeoutMills) {
+            Log.d(TAG, "waitForCompletion tid=" + Thread.currentThread().getId());
+            int count = 0;
+            try {
+                while (count < mWork.length) {
+                    if (!mCountingSemaphore.tryAcquire(timeoutMills, TimeUnit.MILLISECONDS)) {
+                        break;
+                    }
+                    count++;
+                }
+            } catch (InterruptedException e) {
+            }
+            return count;
+        }
+    }
+
+    /**
+     * This gets called by AsyncQueryServiceHelper to read or write the data. It
+     * also verifies the data against the data passed in the constructor
+     */
+    class TestProvider extends ContentProvider {
+        OperationInfo[] mWork;
+
+        int index = 0;
+
+        public TestProvider(OperationInfo[] work) {
+            mWork = work;
+        }
+
+        @Override
+        public final Cursor query(Uri uri, String[] projection, String selection,
+                String[] selectionArgs, String orderBy) {
+            Log.d(TAG, "Provider query index=" + index);
+            assertEquals(mWork[index].op, Operation.EVENT_ARG_QUERY);
+            assertEquals(mWork[index].uri, uri);
+            assertEquals(mWork[index].projection, projection);
+            assertEquals(mWork[index].selection, selection);
+            assertEquals(mWork[index].selectionArgs, selectionArgs);
+            assertEquals(mWork[index].orderBy, orderBy);
+            return (Cursor) mWork[index++].result;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            Log.d(TAG, "Provider insert index=" + index);
+            assertEquals(mWork[index].op, Operation.EVENT_ARG_INSERT);
+            assertEquals(mWork[index].uri, uri);
+            assertEquals(mWork[index].values, values);
+            return (Uri) mWork[index++].result;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+            Log.d(TAG, "Provider update index=" + index);
+            assertEquals(mWork[index].op, Operation.EVENT_ARG_UPDATE);
+            assertEquals(mWork[index].uri, uri);
+            assertEquals(mWork[index].values, values);
+            assertEquals(mWork[index].selection, selection);
+            assertEquals(mWork[index].selectionArgs, selectionArgs);
+            return (Integer) mWork[index++].result;
+        }
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            Log.d(TAG, "Provider delete index=" + index);
+            assertEquals(mWork[index].op, Operation.EVENT_ARG_DELETE);
+            assertEquals(mWork[index].uri, uri);
+            assertEquals(mWork[index].selection, selection);
+            assertEquals(mWork[index].selectionArgs, selectionArgs);
+            return (Integer) mWork[index++].result;
+        }
+
+        @Override
+        public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
+            Log.d(TAG, "Provider applyBatch index=" + index);
+            assertEquals(mWork[index].op, Operation.EVENT_ARG_BATCH);
+            assertEquals(mWork[index].cpo, operations);
+            return (ContentProviderResult[]) mWork[index++].result;
+        }
+
+        @Override
+        public String getType(Uri uri) {
+            return null;
+        }
+
+        @Override
+        public boolean onCreate() {
+            return false;
+        }
+    }
+}
diff --git a/tests/src/com/android/calendar/FormatDateRangeTest.java b/tests/src/com/android/calendar/FormatDateRangeTest.java
index 38d6bc0..d6f5684 100644
--- a/tests/src/com/android/calendar/FormatDateRangeTest.java
+++ b/tests/src/com/android/calendar/FormatDateRangeTest.java
@@ -16,11 +16,10 @@
 
 package com.android.calendar;
 
-import android.content.res.Resources;
-import android.text.format.DateUtils;
-import android.text.format.Time;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.text.format.DateUtils;
+import android.text.format.Time;
 import android.util.Log;
 
 import java.util.Calendar;
@@ -72,8 +71,6 @@
         }
     }
 
-    private Resources mResources;
-
     DateTest[] tests = {
             new DateTest(0, 10, 9, 8, 0, 0, 10, 9, 11, 0,
                     DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8am \u2013 11am"),
@@ -178,8 +175,6 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-
-        mResources = mContext.getResources();
     }
 
     @MediumTest
diff --git a/tests/src/com/android/calendar/UtilsTests.java b/tests/src/com/android/calendar/UtilsTests.java
index 57d301b..41423cb 100644
--- a/tests/src/com/android/calendar/UtilsTests.java
+++ b/tests/src/com/android/calendar/UtilsTests.java
@@ -16,14 +16,13 @@
 
 package com.android.calendar;
 
-import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.Smoke;
 
 import java.util.HashMap;
-import java.util.Map;
+
+import junit.framework.TestCase;
 
 /**
  * Test class for verifying helper functions in Calendar's Utils
@@ -71,9 +70,23 @@
         mDuplicateNameCursor.close();
     }
 
+    @Smoke
     @SmallTest
     public void testCheckForDuplicateNames() {
         Utils.checkForDuplicateNames(mIsDuplicateName, mDuplicateNameCursor, NAME_COLUMN);
         assertEquals(mIsDuplicateName, mIsDuplicateNameExpected);
     }
+
+    @Smoke
+    @SmallTest
+    public void testEquals() {
+        assertTrue(Utils.equals(null, null));
+        assertFalse(Utils.equals("", null));
+        assertFalse(Utils.equals(null, ""));
+        assertTrue(Utils.equals("",""));
+
+        Integer int1 = new Integer(1);
+        Integer int2 = new Integer(1);
+        assertTrue(Utils.equals(int1, int2));
+    }
 }
diff --git a/tests/src/com/android/calendar/WeekNumberTest.java b/tests/src/com/android/calendar/WeekNumberTest.java
index 01f5746..103e366 100644
--- a/tests/src/com/android/calendar/WeekNumberTest.java
+++ b/tests/src/com/android/calendar/WeekNumberTest.java
@@ -16,11 +16,11 @@
 
 package com.android.calendar;
 
-import android.content.res.Resources;
-import android.text.format.DateUtils;
-import android.text.format.Time;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Smoke;
+import android.text.format.DateUtils;
+import android.text.format.Time;
 import android.util.Log;
 
 
@@ -44,7 +44,7 @@
             allDayDate.set(day, month, year);
             allDayDate.normalize(true /* ignore isDst */);
 
-            this.expectedWeekNumber = expectedWeekNumber; 
+            this.expectedWeekNumber = expectedWeekNumber;
         }
     }
 
@@ -200,6 +200,7 @@
         super.setUp();
     }
 
+    @Smoke
     @SmallTest
     public void testAll() throws Exception {
         int len = tests.length;
diff --git a/tests/src/com/android/calendar/event/EditEventHelperTest.java b/tests/src/com/android/calendar/event/EditEventHelperTest.java
new file mode 100644
index 0000000..9507382
--- /dev/null
+++ b/tests/src/com/android/calendar/event/EditEventHelperTest.java
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2010 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.calendar.event;
+
+import com.android.calendar.AbstractCalendarActivity;
+import com.android.calendar.AsyncQueryService;
+import com.android.calendar.CalendarEventModel;
+import com.android.calendar.R;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.test.AndroidTestCase;
+import android.test.mock.MockResources;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Smoke;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.util.Rfc822Token;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.LinkedHashSet;
+import java.util.TimeZone;
+
+public class EditEventHelperTest extends AndroidTestCase {
+    private static final int TEST_EVENT_ID = 1;
+    private static final int TEST_EVENT_INDEX_ID = 0;
+    private static final long TEST_END = 1272931200000L;
+    private static long TEST_END2 = 1272956400000L;
+    private static final long TEST_START = 1272844800000L;
+    private static long TEST_START2 = 1272870000000L;
+    private static final String LOCAL_TZ = TimeZone.getDefault().getID();
+
+    private static final int SAVE_EVENT_NEW_EVENT = 1;
+    private static final int SAVE_EVENT_MOD_RECUR = 2;
+    private static final int SAVE_EVENT_RECUR_TO_NORECUR = 3;
+    private static final int SAVE_EVENT_NORECUR_TO_RECUR= 4;
+    private static final int SAVE_EVENT_MOD_NORECUR = 5;
+    private static final int SAVE_EVENT_MOD_INSTANCE = 6;
+    private static final int SAVE_EVENT_ALLFOLLOW_TO_NORECUR = 7;
+    private static final int SAVE_EVENT_FIRST_TO_NORECUR = 8;
+    private static final int SAVE_EVENT_FIRST_TO_RECUR = 9;
+    private static final int SAVE_EVENT_ALLFOLLOW_TO_RECUR = 10;
+
+    private static String[] TEST_CURSOR_DATA = new String[] {
+            Integer.toString(TEST_EVENT_ID), // 0 _id
+            "The Question", // 1 title
+            "Evaluating Life, the Universe, and Everything",// 2 description
+            "Earth Mk2", // 3 location
+            "1", // 4 All Day
+            "0", // 5 Has alarm
+            "2", // 6 Calendar id
+            "1272844800000", // 7 dtstart, Monday, May 3rd midnight UTC
+            "1272931200000", // 8 dtend, Tuesday, May 4th midnight UTC
+            "P3652421990D", // 9 duration, (10 million years)
+            "UTC", // 10 event timezone
+            "FREQ=DAILY;WKST=SU", // 11 rrule
+            "unique per calendar stuff", // 12 sync id
+            "0", // 13 transparency
+            "3", // 14 visibility
+            "steve@gmail.com", // 15 owner account
+            "1", // 16 has attendee data
+            null, //17 originalEvent
+    }; // These should match up with EditEventHelper.EVENT_PROJECTION
+
+    private static final String AUTHORITY_URI = "content://EditEventHelperAuthority/";
+    private static final String AUTHORITY = "EditEventHelperAuthority";
+
+    private static final String TEST_ADDRESSES =
+            "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+            "one.two.three@email.grue";
+    private static final String TEST_ADDRESSES2 =
+            "no good, ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+            "different@email.bit";
+
+
+    private static final String TAG = "EEHTest";
+
+    private CalendarEventModel mModel1;
+    private CalendarEventModel mModel2;
+
+    private ContentValues mValues;
+    private ContentValues mExpectedValues;
+
+    private EditEventHelper mHelper;
+    private AbstractCalendarActivity mActivity;
+    private int mCurrentSaveTest = 0;
+
+    @Override
+    public void setUp() {
+        Time time = new Time(Time.TIMEZONE_UTC);
+        time.set(TEST_START);
+        time.timezone = LOCAL_TZ;
+        TEST_START2 = time.normalize(true);
+
+        time.timezone = Time.TIMEZONE_UTC;
+        time.set(TEST_END);
+        time.timezone = LOCAL_TZ;
+        TEST_END2 = time.normalize(true);
+    }
+
+    private class MockAbsCalendarActivity extends AbstractCalendarActivity {
+        @Override
+        public AsyncQueryService getAsyncQueryService() {
+            if (mService == null) {
+                mService = new AsyncQueryService(this) {
+                    @Override
+                    public void startBatch(int token, Object cookie, String authority,
+                            ArrayList<ContentProviderOperation> cpo, long delayMillis) {
+                        mockApplyBatch(authority, cpo);
+                    }
+                };
+            }
+            return mService;
+        }
+
+        @Override
+        public Resources getResources() {
+            Resources res = new MockResources() {
+                @Override
+                // The actual selects singular vs plural as well and in the given language
+                public String getQuantityString(int id, int quantity) {
+                    if (id == R.plurals.Nmins) {
+                        return quantity + " mins";
+                    }
+                    if (id == R.plurals.Nminutes) {
+                        return quantity + " minutes";
+                    }
+                    if (id == R.plurals.Nhours) {
+                        return quantity + " hours";
+                    }
+                    if (id == R.plurals.Ndays) {
+                        return quantity + " days";
+                    }
+                    return id + " " + quantity;
+                }
+            };
+            return res;
+        }
+    }
+
+    private AbstractCalendarActivity buildTestContext() {
+        MockAbsCalendarActivity context = new MockAbsCalendarActivity();
+        return context;
+    }
+
+    private ContentProviderResult[] mockApplyBatch(String authority,
+            ArrayList<ContentProviderOperation> operations) {
+        switch (mCurrentSaveTest) {
+            case SAVE_EVENT_NEW_EVENT:
+                // new recurring event
+                verifySaveEventNewEvent(operations);
+                break;
+            case SAVE_EVENT_MOD_RECUR:
+                // update to recurring event
+                verifySaveEventModifyRecurring(operations);
+                break;
+            case SAVE_EVENT_RECUR_TO_NORECUR:
+                // replace recurring event with non-recurring event
+                verifySaveEventRecurringToNonRecurring(operations);
+                break;
+            case SAVE_EVENT_NORECUR_TO_RECUR:
+                // update non-recurring event with recurring event
+                verifySaveEventNonRecurringToRecurring(operations);
+                break;
+            case SAVE_EVENT_MOD_NORECUR:
+                // update to non-recurring
+                verifySaveEventUpdateNonRecurring(operations);
+                break;
+            case SAVE_EVENT_MOD_INSTANCE:
+                // update to single instance of recurring event
+                verifySaveEventModifySingleInstance(operations);
+                break;
+            case SAVE_EVENT_ALLFOLLOW_TO_NORECUR:
+                // update all following with non-recurring event
+                verifySaveEventModifyAllFollowingWithNonRecurring(operations);
+                break;
+            case SAVE_EVENT_FIRST_TO_NORECUR:
+                // update all following with non-recurring event on first event in series
+                verifySaveEventModifyAllFollowingFirstWithNonRecurring(operations);
+                break;
+            case SAVE_EVENT_FIRST_TO_RECUR:
+                // update all following with recurring event on first event in series
+                verifySaveEventModifyAllFollowingFirstWithRecurring(operations);
+                break;
+            case SAVE_EVENT_ALLFOLLOW_TO_RECUR:
+                // update all following with recurring event on second event in series
+                verifySaveEventModifyAllFollowingWithRecurring(operations);
+                break;
+        }
+        return new ContentProviderResult[] {new ContentProviderResult(5)};
+    }
+
+    private void addOwnerAttendeeToOps(ArrayList<ContentProviderOperation> expectedOps, int id) {
+        addOwnerAttendee();
+        ContentProviderOperation.Builder b;
+        b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(mExpectedValues);
+        b.withValueBackReference(Reminders.EVENT_ID, id);
+        expectedOps.add(b.build());
+    }
+
+    // Some tests set the time values to one day later, this does that move in the values
+    private void moveExpectedTimeValuesForwardOneDay() {
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mExpectedValues.put(Events.DTSTART, TEST_START + dayInMs);
+        mExpectedValues.put(Events.DTEND, TEST_END + dayInMs);
+    }
+
+    // Duplicates the delete and add for changing a single email address
+    private void addAttendeeChangesOps(ArrayList<ContentProviderOperation> expectedOps) {
+        ContentProviderOperation.Builder b =
+            ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
+        b.withSelection(EditEventHelper.ATTENDEES_DELETE_PREFIX + "?)",
+                new String[] {"one.two.three@email.grue"});
+        expectedOps.add(b.build());
+
+        mExpectedValues.clear();
+        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
+        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit");
+        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+        mExpectedValues.put(Attendees.EVENT_ID, TEST_EVENT_ID);
+        b = ContentProviderOperation
+                .newInsert(Attendees.CONTENT_URI)
+                .withValues(mExpectedValues);
+        expectedOps.add(b.build());
+    }
+
+    // This is a commonly added set of values
+    private void addOwnerAttendee() {
+        mExpectedValues.clear();
+        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, mModel1.mOwnerAccount);
+        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
+        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
+    }
+
+    /** Some tests add all the attendees to the db, the names and emails should match
+     * with {@link #TEST_ADDRESSES2} minus the 'no good'
+     */
+    private void addTestAttendees(ArrayList<ContentProviderOperation> ops,
+            boolean newEvent, int id) {
+        ContentProviderOperation.Builder b;
+        mExpectedValues.clear();
+        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
+        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "ad1@email.com");
+        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+
+        if (newEvent) {
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+            b.withValueBackReference(Attendees.EVENT_ID, id);
+        } else {
+            mExpectedValues.put(Attendees.EVENT_ID, id);
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+        }
+        ops.add(b.build());
+
+        mExpectedValues.clear();
+        mExpectedValues.put(Attendees.ATTENDEE_NAME, "First Last");
+        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "first@email.com");
+        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+
+        if (newEvent) {
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+            b.withValueBackReference(Attendees.EVENT_ID, id);
+        } else {
+            mExpectedValues.put(Attendees.EVENT_ID, id);
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+        }
+        ops.add(b.build());
+
+        mExpectedValues.clear();
+        mExpectedValues.put(Attendees.ATTENDEE_NAME, (String)null);
+        mExpectedValues.put(Attendees.ATTENDEE_EMAIL, "different@email.bit");
+        mExpectedValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+        mExpectedValues.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+        mExpectedValues.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+
+        if (newEvent) {
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+            b.withValueBackReference(Attendees.EVENT_ID, id);
+        } else {
+            mExpectedValues.put(Attendees.EVENT_ID, id);
+            b = ContentProviderOperation
+                    .newInsert(Attendees.CONTENT_URI)
+                    .withValues(mExpectedValues);
+        }
+        ops.add(b.build());
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventFailures() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+
+        // saveEvent should return false early if:
+        // -it was set to not ok
+        // -the model was null
+        // -the event doesn't represent the same event as the original event
+        // -there's a uri but an original event is not provided
+        mHelper.mEventOk = false;
+        assertFalse(mHelper.saveEvent(null, null, 0));
+        mHelper.mEventOk = true;
+        assertFalse(mHelper.saveEvent(null, null, 0));
+        mModel2.mId = 13;
+        assertFalse(mHelper.saveEvent(mModel1, mModel2, 0));
+        mModel2.mId = mModel1.mId;
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mModel2.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        assertFalse(mHelper.saveEvent(mModel1, null, 0));
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventNewEvent() {
+        // Creates a model of a new event for saving
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+        mCurrentSaveTest = SAVE_EVENT_NEW_EVENT;
+
+        assertTrue(mHelper.saveEvent(mModel1, null, 0));
+    }
+
+    private boolean verifySaveEventNewEvent(ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int br_id = 0;
+        mExpectedValues = buildTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        mExpectedValues.put(Events.HAS_ATTENDEE_DATA, 1);
+        ContentProviderOperation.Builder b = ContentProviderOperation
+                .newInsert(Events.CONTENT_URI)
+                .withValues(mExpectedValues);
+        expectedOps.add(b.build());
+
+        // This call has a separate unit test so we'll use it to simplify making the expected vals
+        mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminderMinutes,
+                new ArrayList<Integer>(), true);
+
+        addOwnerAttendeeToOps(expectedOps, br_id);
+
+        addTestAttendees(expectedOps, true, br_id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifyRecurring() {
+        // Creates an original and an updated recurring event model
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        // Updating a recurring event with a new attendee list
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+        mCurrentSaveTest = SAVE_EVENT_MOD_RECUR;
+
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
+    }
+
+    private boolean verifySaveEventModifyRecurring(ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int br_id = 0;
+        mExpectedValues = buildTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        // This is tested elsewhere, used for convenience here
+        mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues,
+                EditEventHelper.MODIFY_ALL);
+
+        expectedOps.add(
+                ContentProviderOperation
+                .newUpdate(mModel1.mUri)
+                .withValues(mExpectedValues)
+                .build());
+
+        // This call has a separate unit test so we'll use it to simplify making the expected vals
+        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, false);
+
+        addAttendeeChangesOps(expectedOps);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventRecurringToNonRecurring() {
+        // Creates an original and an updated recurring event model
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        // Updating a recurring event with a new attendee list
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Replace an existing recurring event with a non-recurring event
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END;
+        mCurrentSaveTest = SAVE_EVENT_RECUR_TO_NORECUR;
+
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
+    }
+
+    private boolean verifySaveEventRecurringToNonRecurring(ArrayList<ContentProviderOperation> ops)
+            {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int id = 0;
+        mExpectedValues = buildNonRecurringTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        // This is tested elsewhere, used for convenience here
+        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
+                EditEventHelper.MODIFY_ALL);
+
+        expectedOps.add(ContentProviderOperation.newDelete(mModel1.mUri).build());
+        id = expectedOps.size();
+        expectedOps.add(ContentProviderOperation
+                        .newInsert(Events.CONTENT_URI)
+                        .withValues(mExpectedValues)
+                        .build());
+
+        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addOwnerAttendeeToOps(expectedOps, id);
+
+        addTestAttendees(expectedOps, true, id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventNonRecurringToRecurring() {
+        // Creates an original non-recurring and an updated recurring event model
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        // Updating a recurring event with a new attendee list
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        mModel2.mRrule = null;
+        mModel2.mEnd = TEST_END;
+        mCurrentSaveTest = SAVE_EVENT_NORECUR_TO_RECUR;
+
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
+    }
+
+    private boolean verifySaveEventNonRecurringToRecurring(ArrayList<ContentProviderOperation> ops)
+            {
+        // Changing a non-recurring event to a recurring event should generate the same operations
+        // as just modifying a recurring event.
+        return verifySaveEventModifyRecurring(ops);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventUpdateNonRecurring() {
+        // Creates an original non-recurring and an updated recurring event model
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        // Updating a recurring event with a new attendee list
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        mModel2.mRrule = null;
+        mModel2.mEnd = TEST_END2;
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END2;
+        mCurrentSaveTest = SAVE_EVENT_MOD_NORECUR;
+
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL));
+    }
+
+    private boolean verifySaveEventUpdateNonRecurring(ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int id = TEST_EVENT_ID;
+        mExpectedValues = buildNonRecurringTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        // This is tested elsewhere, used for convenience here
+        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
+                EditEventHelper.MODIFY_ALL);
+        expectedOps.add(
+                ContentProviderOperation
+                .newUpdate(mModel1.mUri)
+                .withValues(mExpectedValues)
+                .build());
+        // This call has a separate unit test so we'll use it to simplify making the expected vals
+        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, false);
+        addAttendeeChangesOps(expectedOps);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifySingleInstance() {
+        // Creates an original non-recurring and an updated recurring event model
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Modify the second instance of the event
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END + dayInMs;
+        mModel1.mStart += dayInMs;
+        mModel1.mOriginalStart = mModel1.mStart;
+
+        mCurrentSaveTest = SAVE_EVENT_MOD_INSTANCE;
+        // Only modify this instance
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_SELECTED));
+    }
+
+    private boolean verifySaveEventModifySingleInstance(ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int id = 0;
+        mExpectedValues = buildNonRecurringTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        // This is tested elsewhere, used for convenience here
+        mHelper.checkTimeDependentFields(mModel1, mModel1, mExpectedValues,
+                EditEventHelper.MODIFY_ALL);
+
+        moveExpectedTimeValuesForwardOneDay();
+        mExpectedValues.put(Events.ORIGINAL_EVENT, mModel2.mSyncId);
+        mExpectedValues.put(Events.ORIGINAL_INSTANCE_TIME, mModel1.mOriginalStart);
+        mExpectedValues.put(Events.ORIGINAL_ALL_DAY, 1);
+
+        ContentProviderOperation.Builder b = ContentProviderOperation
+                .newInsert(Events.CONTENT_URI)
+                .withValues(mExpectedValues);
+        expectedOps.add(b.build());
+
+        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addOwnerAttendeeToOps(expectedOps, id);
+
+        addTestAttendees(expectedOps, true, id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifyAllFollowingWithNonRecurring() {
+        // Creates an original and an updated recurring event model. The update starts on the 2nd
+        // instance of the original.
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mModel2.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Modify the second instance of the event
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END + dayInMs;
+        mModel1.mStart += dayInMs;
+        mModel1.mOriginalStart = mModel1.mStart;
+
+        mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_NORECUR;
+        // Only modify this instance
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
+    }
+
+    private boolean verifySaveEventModifyAllFollowingWithNonRecurring(
+            ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int id = 0;
+        mExpectedValues = buildNonRecurringTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        moveExpectedTimeValuesForwardOneDay();
+        // This has a separate test
+        mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart);
+        id = expectedOps.size();
+        expectedOps.add(ContentProviderOperation
+                .newInsert(Events.CONTENT_URI)
+                .withValues(mExpectedValues)
+                .build());
+
+        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addOwnerAttendeeToOps(expectedOps, id);
+
+        addTestAttendees(expectedOps, true, id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifyAllFollowingFirstWithNonRecurring() {
+        // Creates an original recurring and an updated non-recurring event model for the first
+        // instance. This should replace the original event with a non-recurring event.
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mModel2.mUri = mModel1.mUri;
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Move the event one day but keep original start set to the first instance
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END + dayInMs;
+        mModel1.mStart += dayInMs;
+
+        mCurrentSaveTest = SAVE_EVENT_FIRST_TO_NORECUR;
+        // Only modify this instance
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
+    }
+
+    private boolean verifySaveEventModifyAllFollowingFirstWithNonRecurring(
+            ArrayList<ContentProviderOperation> ops) {
+
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int id = 0;
+        mExpectedValues = buildNonRecurringTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        moveExpectedTimeValuesForwardOneDay();
+
+        expectedOps.add(ContentProviderOperation.newDelete(mModel1.mUri).build());
+        id = expectedOps.size();
+        expectedOps.add(ContentProviderOperation
+                        .newInsert(Events.CONTENT_URI)
+                        .withValues(mExpectedValues)
+                        .build());
+
+        mHelper.saveRemindersWithBackRef(expectedOps, id, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addOwnerAttendeeToOps(expectedOps, id);
+
+        addTestAttendees(expectedOps, true, id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifyAllFollowingFirstWithRecurring() {
+        // Creates an original recurring and an updated recurring event model for the first instance
+        // This should replace the original event with a new recurrence
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mModel2.mUri = mModel1.mUri;
+        // And a new start time to ensure the time fields aren't removed
+        mModel1.mOriginalStart = TEST_START;
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Move the event one day but keep original start set to the first instance
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mModel1.mStart += dayInMs;
+
+        mCurrentSaveTest = SAVE_EVENT_FIRST_TO_RECUR;
+        // Only modify this instance
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
+    }
+
+    private boolean verifySaveEventModifyAllFollowingFirstWithRecurring(
+            ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int br_id = 0;
+        mExpectedValues = buildTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        moveExpectedTimeValuesForwardOneDay();
+        mExpectedValues.put(Events.DTEND, (Long)null);
+        // This is tested elsewhere, used for convenience here
+        mHelper.checkTimeDependentFields(mModel2, mModel1, mExpectedValues,
+                EditEventHelper.MODIFY_ALL_FOLLOWING);
+
+        expectedOps.add(
+                ContentProviderOperation
+                .newUpdate(mModel1.mUri)
+                .withValues(mExpectedValues)
+                .build());
+
+        // This call has a separate unit test so we'll use it to simplify making the expected vals
+        mHelper.saveReminders(expectedOps, TEST_EVENT_ID, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addAttendeeChangesOps(expectedOps);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveEventModifyAllFollowingWithRecurring() {
+        // Creates an original recurring and an updated recurring event model
+        // for the second instance. This should end the original recurrence and add a new
+        // recurrence.
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+//        mModel1.mAttendees = TEST_ADDRESSES2;
+
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mModel2.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+
+        // The original model is assumed correct so drop the no good bit
+//        mModel2.mAttendees = "ad1@email.com, \"First Last\" <first@email.com> (comment), " +
+//            "one.two.three@email.grue";
+
+        // Move the event one day and the original start so it references the second instance
+        long dayInMs = EditEventHelper.DAY_IN_SECONDS*1000;
+        mModel1.mStart += dayInMs;
+        mModel1.mOriginalStart = mModel1.mStart;
+
+        mCurrentSaveTest = SAVE_EVENT_ALLFOLLOW_TO_RECUR;
+        // Only modify this instance
+        assertTrue(mHelper.saveEvent(mModel1, mModel2, EditEventHelper.MODIFY_ALL_FOLLOWING));
+    }
+
+    private boolean verifySaveEventModifyAllFollowingWithRecurring(
+            ArrayList<ContentProviderOperation> ops) {
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        int br_id = 0;
+        mExpectedValues = buildTestValues();
+        mExpectedValues.put(Events.HAS_ALARM, 0);
+        moveExpectedTimeValuesForwardOneDay();
+        mExpectedValues.put(Events.DTEND, (Long)null);
+        // This is tested elsewhere, used for convenience here
+        mHelper.updatePastEvents(expectedOps, mModel2, mModel1.mOriginalStart);
+
+        br_id = expectedOps.size();
+        expectedOps.add(ContentProviderOperation
+                .newInsert(Events.CONTENT_URI)
+                .withValues(mExpectedValues)
+                .build());
+
+        // This call has a separate unit test so we'll use it to simplify making the expected vals
+        mHelper.saveRemindersWithBackRef(expectedOps, br_id, mModel1.mReminderMinutes,
+                mModel2.mReminderMinutes, true);
+
+        addOwnerAttendeeToOps(expectedOps, br_id);
+
+        addTestAttendees(expectedOps, true, br_id);
+
+        assertEquals(ops, expectedOps);
+        return true;
+    }
+
+    @Smoke
+    @SmallTest
+    public void testGetAddressesFromList() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        LinkedHashSet<Rfc822Token> expected = new LinkedHashSet<Rfc822Token>();
+        expected.add(new Rfc822Token(null, "ad1@email.com", ""));
+        expected.add(new Rfc822Token("First Last", "first@email.com", "comment"));
+        expected.add(new Rfc822Token(null, "one.two.three@email.grue", ""));
+
+        LinkedHashSet<Rfc822Token> actual = mHelper.getAddressesFromList(TEST_ADDRESSES, null);
+        assertEquals(actual, expected);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testConstructDefaultStartTime() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        long now = 0;
+        long expected = now + 30 * DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+        // 2:00 -> 2:30
+        now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST)
+        expected = now + 30 * DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+        // 2:01 -> 2:30
+        now += DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+        // 2:02 -> 2:30
+        now += DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+        // 2:32 -> 3:00
+        now += 30 * DateUtils.MINUTE_IN_MILLIS;
+        expected += 30 * DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+        // 2:33 -> 3:00
+        now += DateUtils.MINUTE_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultStartTime(now));
+
+    }
+
+    @Smoke
+    @SmallTest
+    public void testConstructDefaultEndTime() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        long start = 1262340000000L;
+        long expected = start + DateUtils.HOUR_IN_MILLIS;
+        assertEquals(expected, mHelper.constructDefaultEndTime(start));
+    }
+
+    @Smoke
+    @SmallTest
+    public void testCheckTimeDependentFieldsNoChanges() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+        mModel2.mRrule = null;
+
+        mValues = buildTestValues();
+        mExpectedValues = buildTestValues();
+
+        // if any time/recurrence vals are different but there's no new rrule it
+        // shouldn't change
+        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues, EditEventHelper.MODIFY_ALL);
+        assertEquals(mValues, mExpectedValues);
+
+        // also, if vals are different and it's not modifying all it shouldn't
+        // change.
+        mModel2.mRrule = "something else";
+        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
+                EditEventHelper.MODIFY_SELECTED);
+        assertEquals(mValues, mExpectedValues);
+
+        // if vals changed and modify all is selected dtstart should be updated
+        // by the difference
+        // between originalStart and start
+        mModel2.mOriginalStart = mModel2.mStart + 60000; // set the old time to
+                                                         // one minute later
+        mModel2.mStart += 120000; // move the event another 1 minute.
+
+        // shouldn't change for an allday event
+        // expectedVals.put(Events.DTSTART, mModel1.mStart + 60000); // should
+        // now be 1 minute later
+        // dtstart2 shouldn't change since it gets rezeroed in the local
+        // timezone for allDay events
+
+        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
+                EditEventHelper.MODIFY_SELECTED);
+        assertEquals(mValues, mExpectedValues);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testCheckTimeDependentFieldsChanges() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mModel1 = buildTestModel();
+        mModel2 = buildTestModel();
+        mModel2.mRrule = null;
+
+        mValues = buildTestValues();
+        mExpectedValues = buildTestValues();
+
+        // if all the time values are the same it should remove them from vals
+        mModel2.mRrule = mModel1.mRrule;
+        mModel2.mStart = mModel1.mStart;
+        mModel2.mOriginalStart = mModel2.mStart;
+
+        mExpectedValues.remove(Events.DTSTART);
+        mExpectedValues.remove(Events.DTEND);
+        mExpectedValues.remove(Events.DURATION);
+        mExpectedValues.remove(Events.ALL_DAY);
+        mExpectedValues.remove(Events.RRULE);
+        mExpectedValues.remove(Events.EVENT_TIMEZONE);
+
+        mHelper.checkTimeDependentFields(mModel1, mModel2, mValues,
+                EditEventHelper.MODIFY_SELECTED);
+        assertEquals(mValues, mExpectedValues);
+
+    }
+
+    @Smoke
+    @SmallTest
+    public void testUpdatePastEvents() {
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        long initialBeginTime = 1472864400000L; // Sep 3, 2016, 12AM UTC time
+        mValues = new ContentValues();
+
+        mModel1 = buildTestModel();
+        mModel1.mUri = Uri.parse(AUTHORITY_URI + TEST_EVENT_ID);
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903;WKST=SU"); // yyyymmddThhmmssZ
+        mValues.put(Events.DTSTART, TEST_START);
+
+        ContentProviderOperation.Builder b =
+                ContentProviderOperation.newUpdate(mModel1.mUri).withValues(mValues);
+        expectedOps.add(b.build());
+
+        mHelper.updatePastEvents(ops, mModel1, initialBeginTime);
+        assertEquals(ops, expectedOps);
+
+        mModel1.mAllDay = false;
+
+        mValues.put(Events.RRULE, "FREQ=DAILY;UNTIL=20160903T005959Z;WKST=SU"); // yyyymmddThhmmssZ
+
+        expectedOps.clear();
+        b = ContentProviderOperation.newUpdate(mModel1.mUri).withValues(mValues);
+        expectedOps.add(b.build());
+
+        ops.clear();
+        mHelper.updatePastEvents(ops, mModel1, initialBeginTime);
+        assertEquals(ops, expectedOps);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testConstructReminderLabel() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+
+        String label = mHelper.constructReminderLabel(35, true);
+        assertEquals(label, "35 mins");
+
+        label = mHelper.constructReminderLabel(72, false);
+        assertEquals(label, "72 minutes");
+
+        label = mHelper.constructReminderLabel(60, true);
+        assertEquals(label, "1 hours");
+
+        label = mHelper.constructReminderLabel(60 * 48, true);
+        assertEquals(label, "2 days");
+    }
+
+    @Smoke
+    @SmallTest
+    public void testIsSameEvent() {
+        mModel1 = new CalendarEventModel();
+        mModel2 = new CalendarEventModel();
+
+        mModel1.mId = 1;
+        mModel1.mCalendarId = 1;
+        mModel2.mId = 1;
+        mModel2.mCalendarId = 1;
+
+        // considered the same if the event and calendar ids both match
+        assertTrue(EditEventHelper.isSameEvent(mModel1, mModel2));
+
+        mModel2.mId = 2;
+        assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2));
+
+        mModel2.mId = 1;
+        mModel2.mCalendarId = 2;
+        assertFalse(EditEventHelper.isSameEvent(mModel1, mModel2));
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveReminders() {
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        long eventId = TEST_EVENT_ID;
+        ArrayList<Integer> reminderMinutes = new ArrayList<Integer>();
+        ArrayList<Integer> originalMinutes = new ArrayList<Integer>();
+        boolean forceSave = true;
+        boolean result;
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+        assertNotNull(mHelper);
+
+        // First test forcing a delete with no reminders.
+        String where = Reminders.EVENT_ID + "=?";
+        String[] args = new String[] {Long.toString(eventId)};
+        ContentProviderOperation.Builder b =
+                ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
+        b.withSelection(where, args);
+        expectedOps.add(b.build());
+
+        result = mHelper.saveReminders(ops, eventId, reminderMinutes, originalMinutes, forceSave);
+        assertTrue(result);
+        assertEquals(ops, expectedOps);
+
+        // Now test calling save with identical reminders and no forcing
+        reminderMinutes.add(5);
+        reminderMinutes.add(10);
+        reminderMinutes.add(15);
+
+        originalMinutes.add(5);
+        originalMinutes.add(10);
+        originalMinutes.add(15);
+
+        forceSave = false;
+
+        ops.clear();
+
+        // Should fail to create any ops since nothing changed
+        result = mHelper.saveReminders(ops, eventId, reminderMinutes, originalMinutes, forceSave);
+        assertFalse(result);
+        assertEquals(ops.size(), 0);
+
+        //Now test adding a single reminder
+        originalMinutes.remove(2);
+
+        addExpectedMinutes(expectedOps);
+
+        result = mHelper.saveReminders(ops, eventId, reminderMinutes, originalMinutes, forceSave);
+        assertTrue(result);
+        assertEquals(ops, expectedOps);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSaveRemindersWithBackRef() {
+        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+        ArrayList<ContentProviderOperation> expectedOps = new ArrayList<ContentProviderOperation>();
+        long eventId = TEST_EVENT_ID;
+        ArrayList<Integer> reminderMinutes = new ArrayList<Integer>();
+        ArrayList<Integer> originalMinutes = new ArrayList<Integer>();
+        boolean forceSave = true;
+        boolean result;
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+        assertNotNull(mHelper);
+
+        // First test forcing a delete with no reminders.
+        ContentProviderOperation.Builder b =
+                ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
+        b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
+        b.withSelectionBackReference(0, TEST_EVENT_INDEX_ID);
+        expectedOps.add(b.build());
+
+        result =
+                mHelper.saveRemindersWithBackRef(ops, TEST_EVENT_INDEX_ID, reminderMinutes,
+                        originalMinutes, forceSave);
+        assertTrue(result);
+        assertEquals(ops, expectedOps);
+
+        // Now test calling save with identical reminders and no forcing
+        reminderMinutes.add(5);
+        reminderMinutes.add(10);
+        reminderMinutes.add(15);
+
+        originalMinutes.add(5);
+        originalMinutes.add(10);
+        originalMinutes.add(15);
+
+        forceSave = false;
+
+        ops.clear();
+
+        result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminderMinutes, originalMinutes,
+                        forceSave);
+        assertFalse(result);
+        assertEquals(ops.size(), 0);
+
+        //Now test adding a single reminder
+        originalMinutes.remove(2);
+
+        addExpectedMinutesWithBackRef(expectedOps);
+
+        result = mHelper.saveRemindersWithBackRef(ops, ops.size(), reminderMinutes, originalMinutes,
+                        forceSave);
+        assertTrue(result);
+        assertEquals(ops, expectedOps);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testIsFirstEventInSeries() {
+        mModel1 = new CalendarEventModel();
+        mModel2 = new CalendarEventModel();
+
+        // It's considered the first event if the original start of the new model matches the
+        // start of the old model
+        mModel1.mOriginalStart = 100;
+        mModel1.mStart = 200;
+        mModel2.mOriginalStart = 100;
+        mModel2.mStart = 100;
+
+        assertTrue(EditEventHelper.isFirstEventInSeries(mModel1, mModel2));
+
+        mModel1.mOriginalStart = 80;
+        assertFalse(EditEventHelper.isFirstEventInSeries(mModel1, mModel2));
+    }
+
+    @Smoke
+    @SmallTest
+    public void testAddRecurrenceRule() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+        mValues = new ContentValues();
+        mExpectedValues = new ContentValues();
+        mModel1 = new CalendarEventModel();
+
+        mExpectedValues.put(Events.RRULE, "Weekly, Monday");
+        mExpectedValues.put(Events.DURATION, "P60S");
+        mExpectedValues.put(Events.DTEND, (Long) null);
+
+        mModel1.mRrule = "Weekly, Monday";
+        mModel1.mStart = 1;
+        mModel1.mEnd = 60001;
+        mModel1.mAllDay = false;
+
+        mHelper.addRecurrenceRule(mValues, mModel1);
+        assertEquals(mValues, mExpectedValues);
+
+        mExpectedValues.put(Events.DURATION, "P1D");
+
+        mModel1.mAllDay = true;
+        mValues.clear();
+
+        mHelper.addRecurrenceRule(mValues, mModel1);
+        assertEquals(mValues, mExpectedValues);
+
+    }
+
+    @Smoke
+    @SmallTest
+    public void testUpdateRecurrenceRule() {
+        int selection = EditEventHelper.DOES_NOT_REPEAT;
+        int weekStart = Calendar.SUNDAY;
+        mModel1 = new CalendarEventModel();
+        mModel1.mTimezone = Time.TIMEZONE_UTC;
+        mModel1.mStart = 1272665741000L; // Fri, April 30th ~ 3:17PM
+
+        mModel1.mRrule = "This should go away";
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertNull(mModel1.mRrule);
+
+        mModel1.mRrule = "This shouldn't change";
+        selection = EditEventHelper.REPEATS_CUSTOM;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "This shouldn't change");
+
+        selection = EditEventHelper.REPEATS_DAILY;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=DAILY;WKST=SU");
+
+        selection = EditEventHelper.REPEATS_EVERY_WEEKDAY;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR");
+
+        selection = EditEventHelper.REPEATS_WEEKLY_ON_DAY;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=WEEKLY;WKST=SU;BYDAY=FR");
+
+        selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=30");
+
+        selection = EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=MONTHLY;WKST=SU;BYDAY=-1FR");
+
+        selection = EditEventHelper.REPEATS_YEARLY;
+
+        EditEventHelper.updateRecurrenceRule(selection, mModel1, weekStart);
+        assertEquals(mModel1.mRrule, "FREQ=YEARLY;WKST=SU");
+    }
+
+    @Smoke
+    @SmallTest
+    public void testSetModelFromCursor() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+        MatrixCursor c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
+        c.addRow(TEST_CURSOR_DATA);
+
+        mModel1 = new CalendarEventModel();
+        mModel2 = buildTestModel();
+
+        EditEventHelper.setModelFromCursor(mModel1, c);
+        assertEquals(mModel1, mModel2);
+
+        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "0";
+        c.close();
+        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
+        c.addRow(TEST_CURSOR_DATA);
+
+        mModel2.mAllDay = false;
+        mModel2.mStart = TEST_START; // UTC time
+
+        EditEventHelper.setModelFromCursor(mModel1, c);
+        assertEquals(mModel1, mModel2);
+
+        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_RRULE] = null;
+        c.close();
+        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
+        c.addRow(TEST_CURSOR_DATA);
+
+        mModel2.mRrule = null;
+        mModel2.mEnd = TEST_END;
+        mModel2.mDuration = null;
+
+        EditEventHelper.setModelFromCursor(mModel1, c);
+        assertEquals(mModel1, mModel2);
+
+        TEST_CURSOR_DATA[EditEventHelper.EVENT_INDEX_ALL_DAY] = "1";
+        c.close();
+        c = new MatrixCursor(EditEventHelper.EVENT_PROJECTION);
+        c.addRow(TEST_CURSOR_DATA);
+
+        mModel2.mAllDay = true;
+        mModel2.mStart = TEST_START; // Monday, May 3rd, midnight
+        mModel2.mEnd = TEST_END; // Tuesday, May 4th, midnight
+
+        EditEventHelper.setModelFromCursor(mModel1, c);
+        assertEquals(mModel1, mModel2);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testGetContentValuesFromModel() {
+        mActivity = buildTestContext();
+        mHelper = new EditEventHelper(mActivity, null);
+        mExpectedValues = buildTestValues();
+        mModel1 = buildTestModel();
+
+        ContentValues values = mHelper.getContentValuesFromModel(mModel1);
+        assertEquals(values, mExpectedValues);
+
+        mModel1.mRrule = null;
+        mModel1.mEnd = TEST_END;
+
+        mExpectedValues.put(Events.RRULE, (String) null);
+        mExpectedValues.put(Events.DURATION, (String) null);
+        mExpectedValues.put(Events.DTEND, TEST_END); // UTC time
+
+        values = mHelper.getContentValuesFromModel(mModel1);
+        assertEquals(values, mExpectedValues);
+
+        mModel1.mAllDay = false;
+
+        mExpectedValues.put(Events.ALL_DAY, 0);
+        mExpectedValues.put(Events.DTSTART, TEST_START);
+        mExpectedValues.put(Events.DTEND, TEST_END);
+        // not an allday event so timezone isn't modified
+        mExpectedValues.put(Events.EVENT_TIMEZONE, "UTC");
+
+        values = mHelper.getContentValuesFromModel(mModel1);
+        assertEquals(values, mExpectedValues);
+    }
+
+    @Smoke
+    @SmallTest
+    public void testExtractDomain() {
+        String domain = EditEventHelper.extractDomain("test.email@gmail.com");
+        assertEquals(domain, "gmail.com");
+
+        domain = EditEventHelper.extractDomain("bademail.no#$%at symbol");
+        assertNull(domain);
+    }
+
+    private void addExpectedMinutes(ArrayList<ContentProviderOperation> expectedOps) {
+        ContentProviderOperation.Builder b;
+        mValues = new ContentValues();
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 5);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        expectedOps.add(b.build());
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 10);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        expectedOps.add(b.build());
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 15);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        mValues.put(Reminders.EVENT_ID, TEST_EVENT_ID);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        expectedOps.add(b.build());
+    }
+
+    private void addExpectedMinutesWithBackRef(ArrayList<ContentProviderOperation> expectedOps) {
+        ContentProviderOperation.Builder b;
+        mValues = new ContentValues();
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 5);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
+        expectedOps.add(b.build());
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 10);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
+        expectedOps.add(b.build());
+
+        mValues.clear();
+        mValues.put(Reminders.MINUTES, 15);
+        mValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(mValues);
+        b.withValueBackReference(Reminders.EVENT_ID, TEST_EVENT_INDEX_ID);
+        expectedOps.add(b.build());
+    }
+
+    private static void assertEquals(ArrayList<ContentProviderOperation> expected,
+            ArrayList<ContentProviderOperation> actual) {
+        if (expected == null) {
+            assertNull(actual);
+        }
+        int size = expected.size();
+
+        assertEquals(size, actual.size());
+
+        for (int i = 0; i < size; i++) {
+            assertTrue(cpoEquals(expected.get(i), actual.get(i)));
+        }
+
+    }
+
+    private static boolean cpoEquals(ContentProviderOperation cpo1, ContentProviderOperation cpo2) {
+        if (cpo1 == null && cpo2 != null) {
+            return false;
+        }
+        if (cpo1 == cpo2) {
+            return true;
+        }
+        if (cpo2 == null) {
+            return false;
+        }
+
+        return TextUtils.equals(cpo1.toString(), cpo2.toString());
+    }
+
+    // Generates a default model for testing. Should match up with
+    // generateTestValues
+    private CalendarEventModel buildTestModel() {
+        CalendarEventModel model = new CalendarEventModel();
+        model.mId = TEST_EVENT_ID;
+        model.mTitle = "The Question";
+        model.mDescription = "Evaluating Life, the Universe, and Everything";
+        model.mLocation = "Earth Mk2";
+        model.mAllDay = true;
+        model.mHasAlarm = false;
+        model.mCalendarId = 2;
+        model.mStart = TEST_START; // Monday, May 3rd, local Time
+        model.mDuration = "P3652421990D";
+        // The model uses the local timezone for allday
+        model.mTimezone = "UTC";
+        model.mRrule = "FREQ=DAILY;WKST=SU";
+        model.mSyncId = "unique per calendar stuff";
+        model.mTransparency = false;
+        model.mVisibility = 2; // This is one less than the values written if >0
+        model.mOwnerAccount = "steve@gmail.com";
+        model.mHasAttendeeData = true;
+
+
+        return model;
+    }
+
+    // Generates a default set of values for testing. Should match up with
+    // generateTestModel
+    private ContentValues buildTestValues() {
+        ContentValues values = new ContentValues();
+
+        values.put(Events.CALENDAR_ID, 2L);
+        values.put(Events.EVENT_TIMEZONE, "UTC"); // Allday events are converted
+                                                  // to UTC for the db
+        values.put(Events.TITLE, "The Question");
+        values.put(Events.ALL_DAY, 1);
+        values.put(Events.DTSTART, TEST_START); // Monday, May 3rd, midnight UTC time
+        values.put(Events.HAS_ATTENDEE_DATA, 1);
+
+        values.put(Events.RRULE, "FREQ=DAILY;WKST=SU");
+        values.put(Events.DURATION, "P3652421990D");
+        values.put(Events.DTEND, (Long) null);
+        values.put(Events.DESCRIPTION, "Evaluating Life, the Universe, and Everything");
+        values.put(Events.EVENT_LOCATION, "Earth Mk2");
+        values.put(Events.TRANSPARENCY, 0);
+        values.put(Events.VISIBILITY, 3); // This is one more than the model if
+                                          // >0
+
+        return values;
+    }
+
+    private ContentValues buildNonRecurringTestValues() {
+        ContentValues values = buildTestValues();
+        values.put(Events.DURATION, (String)null);
+        values.put(Events.DTEND, TEST_END);
+        values.put(Events.RRULE, (String)null);
+        return values;
+    }
+
+    // This gets called by EditEventHelper to read or write the data
+    class TestProvider extends ContentProvider {
+        int index = 0;
+
+        public TestProvider() {
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String orderBy) {
+            return null;
+        }
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            return 0;
+        }
+
+        @Override
+        public String getType(Uri uri) {
+            return null;
+        }
+
+        @Override
+        public boolean onCreate() {
+            return false;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+    }
+
+}
diff --git a/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java b/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java
new file mode 100644
index 0000000..b851680
--- /dev/null
+++ b/tests/src/com/android/calendar/widget/CalendarAppWidgetServiceTest.java
@@ -0,0 +1,304 @@
+/*
+**
+** Copyright 2010, 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.calendar.widget;
+
+import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo;
+import com.android.calendar.widget.CalendarAppWidgetService.CalendarFactory;
+
+import android.database.MatrixCursor;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.View;
+
+import java.util.TimeZone;
+
+// adb shell am instrument -w -e class com.android.providers.calendar.CalendarAppWidgetServiceTest
+//   com.android.providers.calendar.tests/android.test.InstrumentationTestRunner
+
+public class CalendarAppWidgetServiceTest extends AndroidTestCase {
+    private static final String TAG = "CalendarAppWidgetService";
+
+    private static final String DEFAULT_TIMEZONE = "America/Los_Angeles";
+
+    final long now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST)
+    final long ONE_MINUTE = 60000;
+    final long ONE_HOUR = 60 * ONE_MINUTE;
+    final long HALF_HOUR = ONE_HOUR / 2;
+    final long TWO_HOURS = ONE_HOUR * 2;
+
+    final String title = "Title";
+    final String location = "Location";
+
+
+
+//    TODO Disabled test since this CalendarAppWidgetModel is not used for the no event case
+//
+//    @SmallTest
+//    public void testGetAppWidgetModel_noEvents() throws Exception {
+//        // Input
+//        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+//
+//        // Expected Output
+//        CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
+//        expected.visibNoEvents = View.VISIBLE;
+//
+//        // Test
+//        long now = 1270000000000L;
+//        MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
+//        CalendarAppWidgetModel actual = CalendarAppWidgetService.getAppWidgetModel(
+//                getTestContext(), cursor, events, now);
+//
+//        assertEquals(expected.toString(), actual.toString());
+//    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // we want to run these tests in a predictable timezone
+        TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIMEZONE));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        // this restores the previous default timezone
+        TimeZone.setDefault(null);
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_1Event() throws Exception {
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(getContext());
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+
+        // Input
+        // allDay, begin, end, title, location, eventId
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title, location, 0));
+
+        // Expected Output
+        expected.mDayOfMonth = "1";
+        expected.mDayOfWeek = "FRI";
+
+        EventInfo eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "3am";
+        eventInfo.where = location;
+        eventInfo.title = title;
+        expected.mEventInfos.add(eventInfo);
+
+        // Test
+        CalendarAppWidgetModel actual = CalendarFactory.buildAppWidgetModel(
+                getContext(), cursor, Time.getCurrentTimezone());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+ // TODO re-enable this test when our widget behavior is finalized
+    @Suppress @SmallTest
+    public void testGetAppWidgetModel_2StaggeredEvents() throws Exception {
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(getContext());
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+        long tomorrow = now + DateUtils.DAY_IN_MILLIS;
+        long sunday = tomorrow + DateUtils.DAY_IN_MILLIS;
+
+        // Expected Output
+        expected.mDayOfMonth = "1";
+        expected.mDayOfWeek = "FRI";
+
+        EventInfo eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "2am, Tomorrow";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        ++i;
+        eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "2am, Sun";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        // Input
+        // allDay, begin, end, title, location, eventId
+        i = 0;
+        cursor.addRow(getRow(0, tomorrow, tomorrow + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, sunday, sunday + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+
+        // Test
+        CalendarAppWidgetModel actual = CalendarFactory.buildAppWidgetModel(
+                getContext(), cursor, Time.getCurrentTimezone());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventToday() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(getContext());
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.mDayOfMonth = "1";
+        expected.mDayOfWeek = "FRI";
+
+        EventInfo eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "Today";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i++;
+        eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "3am";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i = 0;
+        cursor.addRow(getRow(1, 1262304000000L, 1262390400000L, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+
+        // Test
+        CalendarAppWidgetModel actual = CalendarFactory.buildAppWidgetModel(
+                getContext(), cursor, Time.getCurrentTimezone());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventTomorrow() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(getContext());
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.mDayOfMonth = "1";
+        expected.mDayOfWeek = "FRI";
+
+        EventInfo eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "3am";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i++;
+        eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "Tomorrow";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i = 0;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(1, 1262390400000L, 1262476800000L, title + i, location + i, 0));
+
+        // Test
+        CalendarAppWidgetModel actual = CalendarFactory.buildAppWidgetModel(
+                getContext(), cursor, Time.getCurrentTimezone());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    @SmallTest
+    public void testGetAppWidgetModel_AllDayEventLater() throws Exception {
+        final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
+        CalendarAppWidgetModel expected = new CalendarAppWidgetModel(getContext());
+        MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
+
+        int i = 0;
+
+        // Expected Output
+        expected.mDayOfMonth = "1";
+        expected.mDayOfWeek = "FRI";
+
+        EventInfo eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "3am";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i++;
+        eventInfo = new EventInfo();
+        eventInfo.visibWhen = View.VISIBLE;
+        eventInfo.visibWhere = View.VISIBLE;
+        eventInfo.visibTitle = View.VISIBLE;
+        eventInfo.when = "Sun";
+        eventInfo.where = location + i;
+        eventInfo.title = title + i;
+        expected.mEventInfos.add(eventInfo);
+
+        i = 0;
+        cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
+        ++i;
+        cursor.addRow(getRow(1, 1262476800000L, 1262563200000L, title + i, location + i, 0));
+
+        // Test
+        CalendarAppWidgetModel actual = CalendarAppWidgetService.CalendarFactory.buildAppWidgetModel(
+                getContext(), cursor, Time.getCurrentTimezone());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
+    private Object[] getRow(int allDay, long begin, long end, String title, String location,
+            long eventId) {
+        Object[] row = new Object[CalendarAppWidgetService.EVENT_PROJECTION.length];
+        row[CalendarAppWidgetService.INDEX_ALL_DAY] = new Integer(allDay);
+        row[CalendarAppWidgetService.INDEX_BEGIN] = new Long(begin);
+        row[CalendarAppWidgetService.INDEX_END] = new Long(end);
+        row[CalendarAppWidgetService.INDEX_TITLE] = new String(title);
+        row[CalendarAppWidgetService.INDEX_EVENT_LOCATION] = new String(location);
+        row[CalendarAppWidgetService.INDEX_EVENT_ID] = new Long(eventId);
+        return row;
+    }
+}