OSDN Git Service

Initial Contribution
authorThe Android Open Source Project <initial-contribution@android.com>
Tue, 21 Oct 2008 14:00:00 +0000 (07:00 -0700)
committerThe Android Open Source Project <initial-contribution@android.com>
Tue, 21 Oct 2008 14:00:00 +0000 (07:00 -0700)
127 files changed:
Android.mk [new file with mode: 0644]
AndroidManifest.xml [new file with mode: 0644]
MODULE_LICENSE_APACHE2 [new file with mode: 0644]
NOTICE [new file with mode: 0644]
res/anim/slide_down_in.xml [new file with mode: 0644]
res/anim/slide_down_out.xml [new file with mode: 0644]
res/anim/slide_left_in.xml [new file with mode: 0644]
res/anim/slide_left_out.xml [new file with mode: 0644]
res/anim/slide_right_in.xml [new file with mode: 0644]
res/anim/slide_right_out.xml [new file with mode: 0644]
res/anim/slide_up_in.xml [new file with mode: 0644]
res/anim/slide_up_out.xml [new file with mode: 0644]
res/drawable-land/dna_1_of_6.png [new file with mode: 0644]
res/drawable-land/dna_2345_of_6.png [new file with mode: 0644]
res/drawable-land/dna_6_of_6.png [new file with mode: 0644]
res/drawable-land/dna_empty.png [new file with mode: 0644]
res/drawable/angenda_item.xml [new file with mode: 0644]
res/drawable/app_calendar.png [new file with mode: 0644]
res/drawable/app_icon.png [new file with mode: 0644]
res/drawable/box_appointment.xml [new file with mode: 0644]
res/drawable/box_appointment_longpress.9.png [new file with mode: 0644]
res/drawable/box_appointment_normal.9.png [new file with mode: 0644]
res/drawable/box_appointment_pressed.9.png [new file with mode: 0644]
res/drawable/box_appointment_selected.9.png [new file with mode: 0644]
res/drawable/box_color_white.9.png [new file with mode: 0644]
res/drawable/calendars_item.xml [new file with mode: 0644]
res/drawable/dna_1_of_6.png [new file with mode: 0644]
res/drawable/dna_2345_of_6.png [new file with mode: 0644]
res/drawable/dna_6_of_6.png [new file with mode: 0644]
res/drawable/dna_empty.png [new file with mode: 0644]
res/drawable/ic_alarm_dark.png [new file with mode: 0644]
res/drawable/ic_alarm_white.png [new file with mode: 0644]
res/drawable/ic_menu_reminder.png [new file with mode: 0644]
res/drawable/ic_menu_show_list.png [new file with mode: 0644]
res/drawable/ic_repeat_dark.png [new file with mode: 0644]
res/drawable/ic_repeat_white.png [new file with mode: 0644]
res/drawable/ic_statusbar_calendar.png [new file with mode: 0644]
res/drawable/line.xml [new file with mode: 0644]
res/drawable/month_view_background.9.png [new file with mode: 0644]
res/drawable/month_view_longpress.9.png [new file with mode: 0644]
res/drawable/month_view_pressed.9.png [new file with mode: 0644]
res/drawable/month_view_selected.9.png [new file with mode: 0644]
res/drawable/month_view_today_background.9.png [new file with mode: 0644]
res/drawable/panel_section_divider.9.png [new file with mode: 0644]
res/drawable/round_rect.xml [new file with mode: 0644]
res/drawable/section_divider.9.png [new file with mode: 0644]
res/drawable/selection.xml [new file with mode: 0644]
res/drawable/stat_notify_calendar.png [new file with mode: 0755]
res/drawable/sym_calendar_event.png [new file with mode: 0644]
res/drawable/title_left_arrow.png [new file with mode: 0644]
res/drawable/title_right_arrow.png [new file with mode: 0644]
res/layout/agenda_activity.xml [new file with mode: 0644]
res/layout/agenda_day.xml [new file with mode: 0644]
res/layout/agenda_item.xml [new file with mode: 0644]
res/layout/agenda_reminder_item.xml [new file with mode: 0644]
res/layout/alert_activity.xml [new file with mode: 0644]
res/layout/alert_item.xml [new file with mode: 0644]
res/layout/alert_toast.xml [new file with mode: 0644]
res/layout/bubble_event.xml [new file with mode: 0644]
res/layout/calendar_item.xml [new file with mode: 0644]
res/layout/calendars_activity.xml [new file with mode: 0644]
res/layout/calendars_dropdown_item.xml [new file with mode: 0644]
res/layout/calendars_item.xml [new file with mode: 0644]
res/layout/day_activity.xml [new file with mode: 0644]
res/layout/edit_event.xml [new file with mode: 0644]
res/layout/edit_reminder_item.xml [new file with mode: 0644]
res/layout/event_activity.xml [new file with mode: 0644]
res/layout/event_info_activity.xml [new file with mode: 0644]
res/layout/ics_import_activity.xml [new file with mode: 0644]
res/layout/month_activity.xml [new file with mode: 0644]
res/layout/month_bubble.xml [new file with mode: 0644]
res/layout/status_bar_event.xml [new file with mode: 0644]
res/layout/view_reminder_item.xml [new file with mode: 0644]
res/layout/week_activity.xml [new file with mode: 0644]
res/values-cs/strings.xml [new file with mode: 0644]
res/values-de-rDE/strings.xml [new file with mode: 0644]
res/values-en-rGB/strings.xml [new file with mode: 0644]
res/values-es-rUS/strings.xml [new file with mode: 0644]
res/values-fr-rFR/strings.xml [new file with mode: 0644]
res/values-it-rIT/strings.xml [new file with mode: 0644]
res/values-land/integers.xml [new file with mode: 0644]
res/values-nl-rNL/strings.xml [new file with mode: 0644]
res/values-zh-rTW/strings.xml [new file with mode: 0644]
res/values/arrays.xml [new file with mode: 0644]
res/values/colors.xml [new file with mode: 0644]
res/values/integers.xml [new file with mode: 0644]
res/values/strings.xml [new file with mode: 0644]
res/values/styles.xml [new file with mode: 0644]
res/xml/preferences.xml [new file with mode: 0644]
src/com/android/calendar/AgendaActivity.java [new file with mode: 0644]
src/com/android/calendar/AgendaAdapter.java [new file with mode: 0644]
src/com/android/calendar/AgendaByDayAdapter.java [new file with mode: 0644]
src/com/android/calendar/AlertActivity.java [new file with mode: 0644]
src/com/android/calendar/AlertAdapter.java [new file with mode: 0644]
src/com/android/calendar/AlertReceiver.java [new file with mode: 0644]
src/com/android/calendar/AlertService.java [new file with mode: 0644]
src/com/android/calendar/CalendarActivity.java [new file with mode: 0644]
src/com/android/calendar/CalendarApplication.java [new file with mode: 0644]
src/com/android/calendar/CalendarData.java [new file with mode: 0644]
src/com/android/calendar/CalendarPreferenceActivity.java [new file with mode: 0644]
src/com/android/calendar/CalendarView.java [new file with mode: 0644]
src/com/android/calendar/DateSpinner.java [new file with mode: 0644]
src/com/android/calendar/DayActivity.java [new file with mode: 0644]
src/com/android/calendar/DayView.java [new file with mode: 0644]
src/com/android/calendar/DeleteEventHelper.java [new file with mode: 0644]
src/com/android/calendar/EditEvent.java [new file with mode: 0644]
src/com/android/calendar/Event.java [new file with mode: 0644]
src/com/android/calendar/EventGeometry.java [new file with mode: 0644]
src/com/android/calendar/EventInfoActivity.java [new file with mode: 0644]
src/com/android/calendar/EventLoader.java [new file with mode: 0644]
src/com/android/calendar/IcsImportActivity.java [new file with mode: 0644]
src/com/android/calendar/LaunchActivity.java [new file with mode: 0644]
src/com/android/calendar/MenuHelper.java [new file with mode: 0644]
src/com/android/calendar/MonthActivity.java [new file with mode: 0644]
src/com/android/calendar/MonthView.java [new file with mode: 0644]
src/com/android/calendar/Navigator.java [new file with mode: 0644]
src/com/android/calendar/SelectCalendarsActivity.java [new file with mode: 0644]
src/com/android/calendar/SelectCalendarsAdapter.java [new file with mode: 0644]
src/com/android/calendar/Utils.java [new file with mode: 0644]
src/com/android/calendar/WeekActivity.java [new file with mode: 0644]
src/com/android/calendar/WeekView.java [new file with mode: 0644]
tests/Android.mk [new file with mode: 0644]
tests/AndroidManifest.xml [new file with mode: 0644]
tests/src/com/android/calendar/CalendarLaunchPerformance.java [new file with mode: 0644]
tests/src/com/android/calendar/CalendarTests.java [new file with mode: 0644]
tests/src/com/android/calendar/FormatDateRangeTest.java [new file with mode: 0644]
tests/src/com/android/calendar/WeekNumberTest.java [new file with mode: 0644]

diff --git a/Android.mk b/Android.mk
new file mode 100644 (file)
index 0000000..06ac103
--- /dev/null
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# TODO: Remove dependency of application on the test runner (android.test.runner) 
+# library.
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := Calendar
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..3b0a141
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Calendar/AndroidManifest.xml
+**
+** Copyright 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.calendar"
+        android:sharedUserId="android.uid.calendar">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <application android:name="CalendarApplication"
+            android:label="@string/app_label" android:icon="@drawable/app_icon"
+            android:taskAffinity="android.task.calendar">
+        <!-- TODO: Remove dependency of application on the test runner
+             (android.test) library. -->
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="LaunchActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="MonthActivity" android:label="@string/month_view"
+            android:theme="@android:style/Theme.NoTitleBar" />
+        <activity android:name="WeekActivity" android:label="@string/week_view"
+            android:theme="@android:style/Theme.NoTitleBar" />
+        <activity android:name="DayActivity" android:label="@string/day_view"
+            android:theme="@android:style/Theme.NoTitleBar" />
+        <activity android:name="AgendaActivity" android:label="@string/agenda_view" />
+        
+        <activity android:name="EditEvent" android:label="@string/event_edit_title"
+            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 android:name="EventInfoActivity" android:label="@string/event_info_title"
+            android:configChanges="orientation|keyboardHidden">
+            
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.item/event" />
+            </intent-filter>
+        </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"
+             android:theme="@style/Alert" android:excludeFromRecents="true" />
+        <receiver android:name="AlertReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.EVENT_REMINDER" />
+                <data android:mimeType="vnd.android.cursor.item/calendar-alert" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.TIME_SET" />
+            </intent-filter>
+        </receiver>
+
+        <service android:name="AlertService" />
+
+        <activity android:name="CalendarTests" android:label="Calendar Tests">
+            <intent-filter>
+                 <action android:name="android.intent.action.MAIN" />
+                 <category android:name="android.intent.category.UNIT_TEST" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/NOTICE b/NOTICE
new file mode 100644 (file)
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/res/anim/slide_down_in.xml b/res/anim/slide_down_in.xml
new file mode 100644 (file)
index 0000000..dd1ca08
--- /dev/null
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromYDelta="-100%p" android:toYDelta="0" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_down_out.xml b/res/anim/slide_down_out.xml
new file mode 100644 (file)
index 0000000..188c817
--- /dev/null
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromYDelta="0" android:toYDelta="100%p" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_left_in.xml b/res/anim/slide_left_in.xml
new file mode 100644 (file)
index 0000000..c72fd87
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_left_out.xml b/res/anim/slide_left_out.xml
new file mode 100644 (file)
index 0000000..e4308ec
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_right_in.xml b/res/anim/slide_right_in.xml
new file mode 100644 (file)
index 0000000..f2f97fc
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_right_out.xml b/res/anim/slide_right_out.xml
new file mode 100644 (file)
index 0000000..1807f10
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_up_in.xml b/res/anim/slide_up_in.xml
new file mode 100644 (file)
index 0000000..675908f
--- /dev/null
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="400"/>
+</set>
diff --git a/res/anim/slide_up_out.xml b/res/anim/slide_up_out.xml
new file mode 100644 (file)
index 0000000..a1303a5
--- /dev/null
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromYDelta="0" android:toYDelta="-100%p" android:duration="400"/>
+</set>
diff --git a/res/drawable-land/dna_1_of_6.png b/res/drawable-land/dna_1_of_6.png
new file mode 100644 (file)
index 0000000..2f6aa9c
Binary files /dev/null and b/res/drawable-land/dna_1_of_6.png differ
diff --git a/res/drawable-land/dna_2345_of_6.png b/res/drawable-land/dna_2345_of_6.png
new file mode 100644 (file)
index 0000000..bfed653
Binary files /dev/null and b/res/drawable-land/dna_2345_of_6.png differ
diff --git a/res/drawable-land/dna_6_of_6.png b/res/drawable-land/dna_6_of_6.png
new file mode 100644 (file)
index 0000000..cf861b6
Binary files /dev/null and b/res/drawable-land/dna_6_of_6.png differ
diff --git a/res/drawable-land/dna_empty.png b/res/drawable-land/dna_empty.png
new file mode 100644 (file)
index 0000000..da09adc
Binary files /dev/null and b/res/drawable-land/dna_empty.png differ
diff --git a/res/drawable/angenda_item.xml b/res/drawable/angenda_item.xml
new file mode 100644 (file)
index 0000000..e63e41c
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:shape="rectangle">
+    
+    <corners android:radius="8dip"/>
+    <solid android:color="#ffffffff"/>
+</shape>
diff --git a/res/drawable/app_calendar.png b/res/drawable/app_calendar.png
new file mode 100644 (file)
index 0000000..5e07a15
Binary files /dev/null and b/res/drawable/app_calendar.png differ
diff --git a/res/drawable/app_icon.png b/res/drawable/app_icon.png
new file mode 100644 (file)
index 0000000..9241090
Binary files /dev/null and b/res/drawable/app_icon.png differ
diff --git a/res/drawable/box_appointment.xml b/res/drawable/box_appointment.xml
new file mode 100644 (file)
index 0000000..e3d76e7
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/box_appointment_pressed" />
+    <item android:state_selected="true" android:drawable="@drawable/box_appointment_selected" />
+    <item android:drawable="@drawable/box_appointment_normal" />
+</selector>
diff --git a/res/drawable/box_appointment_longpress.9.png b/res/drawable/box_appointment_longpress.9.png
new file mode 100644 (file)
index 0000000..b3a63ba
Binary files /dev/null and b/res/drawable/box_appointment_longpress.9.png differ
diff --git a/res/drawable/box_appointment_normal.9.png b/res/drawable/box_appointment_normal.9.png
new file mode 100644 (file)
index 0000000..b5c5684
Binary files /dev/null and b/res/drawable/box_appointment_normal.9.png differ
diff --git a/res/drawable/box_appointment_pressed.9.png b/res/drawable/box_appointment_pressed.9.png
new file mode 100644 (file)
index 0000000..d05e886
Binary files /dev/null and b/res/drawable/box_appointment_pressed.9.png differ
diff --git a/res/drawable/box_appointment_selected.9.png b/res/drawable/box_appointment_selected.9.png
new file mode 100644 (file)
index 0000000..51808d6
Binary files /dev/null and b/res/drawable/box_appointment_selected.9.png differ
diff --git a/res/drawable/box_color_white.9.png b/res/drawable/box_color_white.9.png
new file mode 100644 (file)
index 0000000..aaaa381
Binary files /dev/null and b/res/drawable/box_color_white.9.png differ
diff --git a/res/drawable/calendars_item.xml b/res/drawable/calendars_item.xml
new file mode 100644 (file)
index 0000000..6520b52
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" 
+    android:shape="rectangle">
+    
+    <corners android:radius="6dip"/>
+    <solid android:color="#00ffffff"/>
+</shape>
diff --git a/res/drawable/dna_1_of_6.png b/res/drawable/dna_1_of_6.png
new file mode 100644 (file)
index 0000000..af524bc
Binary files /dev/null and b/res/drawable/dna_1_of_6.png differ
diff --git a/res/drawable/dna_2345_of_6.png b/res/drawable/dna_2345_of_6.png
new file mode 100644 (file)
index 0000000..b2a22d9
Binary files /dev/null and b/res/drawable/dna_2345_of_6.png differ
diff --git a/res/drawable/dna_6_of_6.png b/res/drawable/dna_6_of_6.png
new file mode 100644 (file)
index 0000000..97914c5
Binary files /dev/null and b/res/drawable/dna_6_of_6.png differ
diff --git a/res/drawable/dna_empty.png b/res/drawable/dna_empty.png
new file mode 100644 (file)
index 0000000..257c00a
Binary files /dev/null and b/res/drawable/dna_empty.png differ
diff --git a/res/drawable/ic_alarm_dark.png b/res/drawable/ic_alarm_dark.png
new file mode 100644 (file)
index 0000000..8e0a0cf
Binary files /dev/null and b/res/drawable/ic_alarm_dark.png differ
diff --git a/res/drawable/ic_alarm_white.png b/res/drawable/ic_alarm_white.png
new file mode 100644 (file)
index 0000000..54fa86e
Binary files /dev/null and b/res/drawable/ic_alarm_white.png differ
diff --git a/res/drawable/ic_menu_reminder.png b/res/drawable/ic_menu_reminder.png
new file mode 100644 (file)
index 0000000..23c6936
Binary files /dev/null and b/res/drawable/ic_menu_reminder.png differ
diff --git a/res/drawable/ic_menu_show_list.png b/res/drawable/ic_menu_show_list.png
new file mode 100644 (file)
index 0000000..debced4
Binary files /dev/null and b/res/drawable/ic_menu_show_list.png differ
diff --git a/res/drawable/ic_repeat_dark.png b/res/drawable/ic_repeat_dark.png
new file mode 100644 (file)
index 0000000..9ad78c8
Binary files /dev/null and b/res/drawable/ic_repeat_dark.png differ
diff --git a/res/drawable/ic_repeat_white.png b/res/drawable/ic_repeat_white.png
new file mode 100644 (file)
index 0000000..95bef88
Binary files /dev/null and b/res/drawable/ic_repeat_white.png differ
diff --git a/res/drawable/ic_statusbar_calendar.png b/res/drawable/ic_statusbar_calendar.png
new file mode 100644 (file)
index 0000000..9539623
Binary files /dev/null and b/res/drawable/ic_statusbar_calendar.png differ
diff --git a/res/drawable/line.xml b/res/drawable/line.xml
new file mode 100644 (file)
index 0000000..9ffd504
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" type="line">
+    <stroke android:width="1dp" android:color="#ff000000" />
+    <padding android:left="0dp" android:top="1dp"
+            android:right="0dp" android:bottom="1dp" />
+</shape>
diff --git a/res/drawable/month_view_background.9.png b/res/drawable/month_view_background.9.png
new file mode 100644 (file)
index 0000000..8a8bfcb
Binary files /dev/null and b/res/drawable/month_view_background.9.png differ
diff --git a/res/drawable/month_view_longpress.9.png b/res/drawable/month_view_longpress.9.png
new file mode 100644 (file)
index 0000000..e84b4fa
Binary files /dev/null and b/res/drawable/month_view_longpress.9.png differ
diff --git a/res/drawable/month_view_pressed.9.png b/res/drawable/month_view_pressed.9.png
new file mode 100644 (file)
index 0000000..70c0f94
Binary files /dev/null and b/res/drawable/month_view_pressed.9.png differ
diff --git a/res/drawable/month_view_selected.9.png b/res/drawable/month_view_selected.9.png
new file mode 100644 (file)
index 0000000..67a6e2e
Binary files /dev/null and b/res/drawable/month_view_selected.9.png differ
diff --git a/res/drawable/month_view_today_background.9.png b/res/drawable/month_view_today_background.9.png
new file mode 100644 (file)
index 0000000..42e54f0
Binary files /dev/null and b/res/drawable/month_view_today_background.9.png differ
diff --git a/res/drawable/panel_section_divider.9.png b/res/drawable/panel_section_divider.9.png
new file mode 100644 (file)
index 0000000..46098ae
Binary files /dev/null and b/res/drawable/panel_section_divider.9.png differ
diff --git a/res/drawable/round_rect.xml b/res/drawable/round_rect.xml
new file mode 100644 (file)
index 0000000..37c9237
--- /dev/null
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" type="rectangle">
+    <solid android:color="#00000000"/>
+    <stroke android:width="1dp" android:color="#ff5782c3" />
+    <padding android:left="7dp" android:top="7dp"
+            android:right="7dp" android:bottom="7dp" />
+    <corners android:radius="8dp" />
+</shape>
diff --git a/res/drawable/section_divider.9.png b/res/drawable/section_divider.9.png
new file mode 100644 (file)
index 0000000..46098ae
Binary files /dev/null and b/res/drawable/section_divider.9.png differ
diff --git a/res/drawable/selection.xml b/res/drawable/selection.xml
new file mode 100644 (file)
index 0000000..7ca6b55
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Calendar/assets/res/any/drawable/selection.xml
+**
+** Copyright 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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:startColor="#ffffffff" android:endColor="#ffffaa00"
+            android:angle="270"/>
+    <stroke android:width="1dp" android:color="#ffc1a43a"/>
+    <corners android:radius="0dp"/>
+    <padding android:left="0dp" android:top="0dp"
+            android:right="0dp" android:bottom="0dp" />
+</shape>
diff --git a/res/drawable/stat_notify_calendar.png b/res/drawable/stat_notify_calendar.png
new file mode 100755 (executable)
index 0000000..4433a16
Binary files /dev/null and b/res/drawable/stat_notify_calendar.png differ
diff --git a/res/drawable/sym_calendar_event.png b/res/drawable/sym_calendar_event.png
new file mode 100644 (file)
index 0000000..43428a1
Binary files /dev/null and b/res/drawable/sym_calendar_event.png differ
diff --git a/res/drawable/title_left_arrow.png b/res/drawable/title_left_arrow.png
new file mode 100644 (file)
index 0000000..5734b8a
Binary files /dev/null and b/res/drawable/title_left_arrow.png differ
diff --git a/res/drawable/title_right_arrow.png b/res/drawable/title_right_arrow.png
new file mode 100644 (file)
index 0000000..402051a
Binary files /dev/null and b/res/drawable/title_right_arrow.png differ
diff --git a/res/layout/agenda_activity.xml b/res/layout/agenda_activity.xml
new file mode 100644 (file)
index 0000000..4bf0ee6
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+    
+    <ViewSwitcher android:id="@+id/switcher"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+</LinearLayout>
diff --git a/res/layout/agenda_day.xml b/res/layout/agenda_day.xml
new file mode 100644 (file)
index 0000000..0a50ebc
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent"
+    >
+    
+    <View android:layout_height="1dip"
+        android:layout_width="fill_parent"
+        android:layout_marginTop="5dip"
+        android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent"
+        android:background="@android:color/darker_gray"
+        android:paddingLeft="4dip"
+        >
+        <TextView android:id="@+id/date"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textStyle="bold"
+            android:textColor="?android:attr/textColorPrimary"
+            />
+        <TextView android:id="@+id/day_of_week"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginLeft="5dip"
+            android:textColor="?android:attr/textColorPrimary"
+            />
+    </LinearLayout>
+    
+    <View android:layout_height="1dip"
+        android:layout_width="fill_parent"
+        android:background="@android:drawable/divider_horizontal_dark"
+        android:layout_marginBottom="5dip"
+        />
+        
+</LinearLayout>
diff --git a/res/layout/agenda_item.xml b/res/layout/agenda_item.xml
new file mode 100644 (file)
index 0000000..76386ba
--- /dev/null
@@ -0,0 +1,95 @@
+<?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:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent"
+    android:paddingLeft="1dip"
+    android:background="@drawable/box_appointment"
+    android:layout_marginTop="2dip"
+    android:layout_marginBottom="2dip"
+    android:layout_marginLeft="3dip"
+    android:layout_marginRight="3dip"
+    >
+    
+    <ImageView android:id="@+id/vertical_stripe"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_marginTop="2dip"
+        android:layout_marginBottom="2dip"
+        android:layout_marginRight="5dip"
+        android:background="@drawable/box_color_white" />
+        
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent"
+        android:layout_weight="1">
+        
+        <TextView android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:textColor="@color/black"
+            style="?android:attr/textAppearanceMediumInverse"
+        />
+        
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+            
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:gravity="center_vertical">
+                <TextView android:id="@+id/when"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textStyle="bold"
+                    android:textColor="@color/black"
+                    style="?android:attr/textAppearanceSmallInverse"
+                />
+                
+                <ImageView android:id="@+id/repeat_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_marginLeft="5dip"
+                    android:src="@drawable/ic_repeat_dark"
+                    android:focusable="false"
+                    android:clickable="false"
+                />
+            </LinearLayout>
+            
+            <LinearLayout android:id="@+id/reminders_container"
+                android:orientation="vertical"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+            />
+        </LinearLayout>
+        
+        <TextView android:id="@+id/where"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:textColor="@color/black"
+            style="?android:attr/textAppearanceSmallInverse"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/agenda_reminder_item.xml b/res/layout/agenda_reminder_item.xml
new file mode 100644 (file)
index 0000000..4038360
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent">
+    
+    <ImageView android:id="@+id/reminder_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:src="@drawable/ic_alarm_dark"
+        android:focusable="false"
+        android:clickable="false"
+    />
+    
+    <TextView android:id="@+id/reminder"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.AgendaView_ValueLabel"
+    />
+</LinearLayout>
diff --git a/res/layout/alert_activity.xml b/res/layout/alert_activity.xml
new file mode 100644 (file)
index 0000000..ba0074f
--- /dev/null
@@ -0,0 +1,52 @@
+<?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:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:padding="10dip">
+    
+    <ListView android:id="@+id/alert_container"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:listSelector="@android:color/transparent"
+        android:divider="@null" />
+    
+    <LinearLayout android:id="@+id/button_container"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="3dip">
+
+        <Button android:id="@+id/snooze_all"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/snooze_all_label" />
+
+        <TextView android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+            
+        <Button android:id="@+id/dismiss_all"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/dismiss_all_label" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/alert_item.xml b/res/layout/alert_item.xml
new file mode 100644 (file)
index 0000000..3f389bd
--- /dev/null
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/box_appointment">
+    
+    <ImageView android:id="@+id/vertical_stripe"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_marginTop="2dip"
+        android:layout_marginBottom="2dip"
+        android:layout_marginRight="5dip"
+        android:background="@drawable/box_color_white" />
+        
+    <LinearLayout android:id="@+id/alert_content"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        
+        <LinearLayout android:id="@+id/event_title_container"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+            
+            <TextView android:id="@+id/event_title"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:textStyle="bold"
+                android:textColor="@color/black"
+                style="?android:attr/textAppearanceMediumInverse" />
+        </LinearLayout>
+        
+        <LinearLayout android:id="@+id/event_details"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+            
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent">
+                
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="0dip"
+                    android:layout_height="fill_parent"
+                    android:layout_weight="1"
+                    android:gravity="center_vertical">
+                    
+                    <TextView android:id="@+id/when"
+                        android:layout_width="wrap_content"
+                        android:layout_height="fill_parent"
+                        android:textStyle="bold"
+                        style="?android:attr/textAppearanceSmallInverse" />
+                        
+                    <ImageView android:id="@+id/repeat_icon"
+                        android:layout_width="wrap_content"
+                        android:layout_height="fill_parent"
+                        android:layout_marginLeft="5dip"
+                        android:src="@drawable/ic_repeat_dark"
+                        android:focusable="false"
+                        android:clickable="false"
+                    />
+                
+                </LinearLayout>
+                <LinearLayout android:id="@+id/reminders_container"
+                    android:orientation="vertical"
+                    android:layout_height="fill_parent"
+                    android:layout_width="wrap_content"
+                />
+            </LinearLayout>
+            
+            <TextView android:id="@+id/where"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:textStyle="bold"
+                style="?android:attr/textAppearanceSmallInverse" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/alert_toast.xml b/res/layout/alert_toast.xml
new file mode 100644 (file)
index 0000000..17b1808
--- /dev/null
@@ -0,0 +1,91 @@
+<?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/event"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@android:drawable/toast_frame">
+    
+    <LinearLayout android:id="@+id/event_title_container"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="10dip">
+        
+        <ImageView android:id="@+id/repeat_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="10dip"
+            android:src="@android:drawable/ic_popup_reminder"
+        />
+        
+        <TextView android:id="@+id/event_title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            style="@style/TextAppearance.Alert_Title" />
+    </LinearLayout>
+    
+    <ImageView android:id="@+id/title_separator"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@android:drawable/divider_horizontal_dark" />
+    
+    <LinearLayout android:id="@+id/event_details"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="10dip">
+        
+        <LinearLayout android:id="@+id/when_container"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+            
+            <TextView android:id="@+id/when_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingRight="4dip"
+                android:text="@string/alert_when_label"
+                style="@style/TextAppearance.Alert_Label" />
+            
+            <TextView android:id="@+id/when"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.Alert_Value" />
+        </LinearLayout>
+        
+        <LinearLayout android:id="@+id/where_container"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+            
+            <TextView android:id="@+id/where_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingRight="4dip"
+                android:text="@string/alert_where_label"
+                style="@style/TextAppearance.Alert_Label" />
+    
+            <TextView android:id="@+id/where"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.Alert_Value" />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/bubble_event.xml b/res/layout/bubble_event.xml
new file mode 100644 (file)
index 0000000..0c273c6
--- /dev/null
@@ -0,0 +1,63 @@
+<?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/bubble_layout"
+    android:orientation="vertical"
+    android:paddingTop="5dip"
+    android:paddingLeft="10dip"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+        
+    <TextView android:id="@+id/event_title"
+        android:textStyle="bold"
+        android:textColor="@drawable/panel_text_foreground"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+
+        <ImageView android:id="@+id/reminder_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginRight="6dip"
+            android:src="@drawable/ic_alarm_white"
+        />
+
+        <ImageView android:id="@+id/repeat_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginRight="6dip"
+            android:src="@drawable/ic_repeat_white"
+        />
+
+        <TextView android:id="@+id/time"
+            android:textColor="@drawable/panel_text_foreground"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+    <TextView android:id="@+id/where"
+        android:textColor="@drawable/panel_text_foreground"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/res/layout/calendar_item.xml b/res/layout/calendar_item.xml
new file mode 100644 (file)
index 0000000..2fce8c8
--- /dev/null
@@ -0,0 +1,41 @@
+<?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:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical">
+
+    <View android:id="@+id/color"
+        android:layout_width="10dip"
+        android:layout_height="58dip"
+    />
+
+    <CheckBox android:id="@+id/checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="4dip"
+        android:layout_marginRight="2dip"
+    />
+
+    <TextView android:id="@+id/calendar"
+        android:text="@string/calendar_item_calendar_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?android:attr/textAppearanceLarge"
+    />
+</LinearLayout>
diff --git a/res/layout/calendars_activity.xml b/res/layout/calendars_activity.xml
new file mode 100644 (file)
index 0000000..3eb19b1
--- /dev/null
@@ -0,0 +1,26 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent">
+
+    <ListView android:id="@+id/items"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/res/layout/calendars_dropdown_item.xml b/res/layout/calendars_dropdown_item.xml
new file mode 100644 (file)
index 0000000..5b6bbd6
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent">
+
+    <ImageView android:id="@+id/right_arrow"
+           android:layout_width="wrap_content"
+           android:layout_height="fill_parent"
+           android:src="@drawable/calendars_item"/>
+
+    <CheckedTextView
+        android:id="@+id/calendar_name"
+        style="?android:attr/spinnerDropDownItemStyle"
+        android:singleLine="true"
+        android:layout_width="fill_parent"
+        android:layout_height="?android:attr/listPreferredItemHeight" />
+
+</LinearLayout>
diff --git a/res/layout/calendars_item.xml b/res/layout/calendars_item.xml
new file mode 100644 (file)
index 0000000..1ba2d08
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_height="wrap_content"
+    android:layout_width="fill_parent">
+
+    <ImageView android:id="@+id/right_arrow"
+           android:layout_width="wrap_content"
+           android:layout_height="fill_parent"
+           android:src="@drawable/calendars_item"/>
+           
+    <TextView android:id="@+id/calendar_name"
+        style="?android:attr/spinnerItemStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/res/layout/day_activity.xml b/res/layout/day_activity.xml
new file mode 100644 (file)
index 0000000..aeadd56
--- /dev/null
@@ -0,0 +1,58 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent" >
+     
+    <RelativeLayout 
+        android:layout_width="fill_parent" 
+        android:layout_height="?android:attr/windowTitleSize"
+        style="?android:attr/windowTitleBackgroundStyle">
+
+        <TextView android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center" 
+            style="?android:attr/windowTitleStyle"
+            android:background="@null"
+            android:fadingEdge="horizontal"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+        />
+
+        <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>
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <ViewSwitcher android:id="@+id/switcher"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+</LinearLayout>
diff --git a/res/layout/edit_event.xml b/res/layout/edit_event.xml
new file mode 100644 (file)
index 0000000..3252b9b
--- /dev/null
@@ -0,0 +1,344 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/scroll_view"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <LinearLayout android:id="@+id/event"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+        
+        <!-- WHAT -->
+        <LinearLayout android:id="@+id/what_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip">
+        
+            <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"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_what"
+                android:capitalize="words"/>
+        </LinearLayout>
+        
+        <!-- WHEN -->
+        <View android:id="@+id/when_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/when_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip">
+            
+            <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"/>
+    
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+                
+                <Button android:id="@+id/start_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                
+                <Button android:id="@+id/start_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                    
+            </LinearLayout>
+    
+            <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"/>
+    
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+    
+                <Button android:id="@+id/end_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+    
+                <Button android:id="@+id/end_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            </LinearLayout>
+                
+            <CheckBox android:id="@+id/is_all_day"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/edit_event_all_day_label"/>
+        </LinearLayout>
+
+        <!-- WHERE -->
+        <View android:id="@+id/where_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/where_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip">
+            
+            <TextView android:id="@+id/location_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/where_label"
+                style="@style/TextAppearance.EditEvent_Label"/>
+    
+            <EditText android:id="@+id/location"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_where"
+                android:capitalize="sentences"/>
+        </LinearLayout>
+
+        <!-- DESCRIPTION -->
+        <View android:id="@+id/description_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/description_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="5dip">
+            
+            <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="fill_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/hint_description"
+                android:capitalize="sentences"
+                android:maxLines="4"/>
+        </LinearLayout>
+
+        <!-- CALENDARS -->
+        <View android:id="@+id/calendar_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/calendars_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="5dip"
+            android:paddingRight="5dip"
+            android:paddingTop="5dip"
+            android:paddingBottom="1dip">
+            
+            <TextView android:id="@+id/calendar_label"
+                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="fill_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/repeats_label"/>
+        </LinearLayout>
+        
+        <!-- REMINDERS -->
+        <View android:id="@+id/reminders_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/reminders_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="5dip"
+            android:paddingRight="5dip"
+            android:paddingTop="5dip"
+            android:paddingBottom="1dip">
+            
+            <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="fill_parent"
+                android:layout_height="wrap_content">
+            </LinearLayout>
+        </LinearLayout>
+        
+        <!-- REPEATS -->
+        <View android:id="@+id/repeats_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/repeats_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="5dip"
+            android:paddingRight="5dip"
+            android:paddingTop="5dip"
+            android:paddingBottom="1dip">
+            
+            <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="fill_parent"
+                android:layout_height="wrap_content"/>
+        </LinearLayout>
+        
+        <!-- MORE OPTIONS -->
+        <LinearLayout android:id="@+id/extra_options_container"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone">
+            
+            <!-- PRESENCE -->
+            <View android:id="@+id/presense_separator"
+                android:layout_width="fill_parent"
+                android:layout_height="1dip"
+                android:background="@android:drawable/divider_horizontal_dark"
+            />
+            
+            <LinearLayout android:id="@+id/presence_container"
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="5dip"
+                android:paddingRight="5dip"
+                android:paddingTop="5dip"
+                android:paddingBottom="1dip">
+                
+                <TextView android:id="@+id/presence_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/presence_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+        
+                <Spinner android:id="@+id/availability"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/availability" />
+            </LinearLayout>
+    
+            <!-- PRIVACY -->
+            <View android:id="@+id/privacy_separator"
+                android:layout_width="fill_parent"
+                android:layout_height="1dip"
+                android:background="@android:drawable/divider_horizontal_dark"
+            />
+            
+            <LinearLayout android:id="@+id/privacy_container"
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="5dip"
+                android:paddingRight="5dip"
+                android:paddingTop="5dip"
+                android:paddingBottom="1dip">
+                
+                <TextView android:id="@+id/privacy_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/privacy_label"
+                    style="@style/TextAppearance.EditEvent_Label"/>
+        
+                <Spinner android:id="@+id/visibility"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/visibility" />
+            </LinearLayout>
+        </LinearLayout>
+        
+        <!-- BUTTONS -->
+        <View android:id="@+id/buttons_separator"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@android:drawable/divider_horizontal_dark"
+        />
+        
+        <LinearLayout android:id="@+id/buttons"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="5dip"
+            android:baselineAligned="false">
+            
+            <Button android:id="@+id/save"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/save_label"
+            />
+            
+            <Button android:id="@+id/discard"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/discard_label"
+            />
+            
+            <Button android:id="@+id/delete"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/delete_label"
+            />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/res/layout/edit_reminder_item.xml b/res/layout/edit_reminder_item.xml
new file mode 100644 (file)
index 0000000..745410d
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+    
+    <Spinner android:id="@+id/reminder_value"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:entries="@array/reminder_minutes_labels"/>
+        
+    <ImageButton android:id="@+id/reminder_remove"
+        style="?android:attr/buttonStyleInset"
+        android:src="@android:drawable/ic_delete"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_marginTop="2dip"
+        android:layout_marginRight="2dip"
+        android:layout_marginBottom="2dip"
+        android:gravity="center_vertical"
+    />
+</LinearLayout>
diff --git a/res/layout/event_activity.xml b/res/layout/event_activity.xml
new file mode 100644 (file)
index 0000000..08b6102
--- /dev/null
@@ -0,0 +1,433 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/scroll_view"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="@drawable/event_background" >
+
+    <LinearLayout android:id="@+id/event"
+        android:orientation="vertical"
+        android:paddingLeft="15dip"
+        android:paddingRight="3dip"
+        android:paddingTop="3dip"
+        android:paddingBottom="15dip"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+        <TableLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:shrinkColumns="1">
+
+            <TableRow>
+                <TextView android:id="@+id/what_label"
+                    android:text="@string/what_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <EditText android:id="@+id/event_title"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <ImageView android:id="@+id/what_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow>
+                <TextView android:id="@+id/when_label"
+                    android:text="@string/when_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <CheckBox android:id="@+id/all_day"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/all_day_event" />
+            </TableRow>
+
+            <TableRow>
+                <TextView android:id="@+id/from_label"
+                    android:text="@string/from_label"
+                    android:textColor="@drawable/label_foreground"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <com.android.calendar.DateSpinner android:id="@+id/start_date_spinner"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <TableRow>
+                <com.android.calendar.TimeSpinner android:id="@+id/start_time_spinner"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_column="1" />
+            </TableRow>
+
+            <TableRow>
+                <TextView android:id="@+id/to_label"
+                    android:text="@string/to_label"
+                    android:textColor="@drawable/label_foreground"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <com.android.calendar.TimeSpinner android:id="@+id/end_time_spinner"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <TableRow>
+                <TextView android:id="@+id/to_date_label"
+                    android:text="@string/to_label"
+                    android:textColor="@drawable/label_foreground"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <com.android.calendar.DateSpinner android:id="@+id/end_date_spinner"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <ImageView android:id="@+id/when_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow android:id="@+id/where_row">
+                <TextView android:id="@+id/where_label"
+                    android:text="@string/where_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <EditText android:id="@+id/where"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <ImageView android:id="@+id/where_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow>
+                <TextView android:id="@+id/reminder_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingRight="10dip"
+                    android:text="@string/reminder" />
+
+                <Spinner android:id="@+id/reminder"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/reminder_minutes_labels" />
+            </TableRow>
+
+            <ImageView android:id="@+id/reminder_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow>
+                <ImageView android:id="@+id/calendar_icon"
+                    android:layout_marginTop="4dip"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/sym_calendar_event" />
+
+                <Spinner android:id="@+id/calendars"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <ImageView android:id="@+id/calendar_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow>
+                <TextView android:id="@+id/repeats_label"
+                    android:text="@string/repeats_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <Spinner android:id="@+id/repeats"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/repeat_strings" />
+            </TableRow>
+
+            <TableRow android:id="@+id/every_row">
+                <TextView android:id="@+id/every_label"
+                    android:text="@string/every_label"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <Spinner android:id="@+id/every"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <TableRow android:id="@+id/on_row">
+                <TextView android:id="@+id/on_label"
+                    android:text="@string/on_label"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content">
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_sunday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/sunday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_monday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/monday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_tuesday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/tuesday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_wednesday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/wednesday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_thursday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/thursday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_friday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/friday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:orientation="vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+
+                        <CheckBox android:id="@+id/on_saturday"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+
+                        <TextView
+                            android:text="@string/saturday_letter"
+                            android:textStyle="bold"
+                            android:paddingLeft="3dip"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content" />
+                    </LinearLayout>
+                </LinearLayout>
+            </TableRow>
+
+            <TableRow android:id="@+id/month_row">
+                <TextView
+                    android:text="@string/repeat_on_label"
+                    android:gravity="right|top"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <RadioGroup android:id="@+id/repeat_on_radio_group"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical">
+
+                    <RadioButton android:id="@+id/day_of_week" />
+                    <RadioButton android:id="@+id/day_of_month" />
+                </RadioGroup>
+            </TableRow>
+
+            <TableRow android:id="@+id/until_row">
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:gravity="right"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <CheckBox android:id="@+id/until"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/until" />
+                </LinearLayout>
+
+                <com.android.calendar.DateSpinner android:id="@+id/until_spinner"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <ImageView android:id="@+id/repeats_divider" android:scaleType="fitXY"
+                android:src="@drawable/section_divider"
+                android:layout_marginTop="4dip"
+                android:layout_marginBottom="4dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TableRow>
+                <TextView android:id="@+id/notes_label"
+                    android:text="@string/notes_label"
+                    android:gravity="right|top"
+                    android:textStyle="bold"
+                    android:paddingRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <EditText android:id="@+id/notes"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </TableRow>
+
+            <TableRow>
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_column="1">
+
+                    <Button android:id="@+id/done"
+                        android:layout_marginTop="8dip"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/done_label" />
+
+                    <Button android:id="@+id/options"
+                        android:layout_marginTop="8dip"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/more_options_label" />
+                </LinearLayout>
+            </TableRow>
+        </TableLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/res/layout/event_info_activity.xml b/res/layout/event_info_activity.xml
new file mode 100644 (file)
index 0000000..ebdcfc2
--- /dev/null
@@ -0,0 +1,213 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent">
+    
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1.0" >
+        
+        <LinearLayout android:id="@+id/event"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true" >
+
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:paddingLeft="1dip"
+                android:background="@drawable/box_appointment"
+                android:layout_marginTop="2dip"
+                android:layout_marginBottom="2dip"
+                android:layout_marginLeft="3dip"
+                android:layout_marginRight="3dip"
+                >
+    
+                <ImageView android:id="@+id/vertical_stripe"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_marginTop="2dip"
+                    android:layout_marginBottom="2dip"
+                    android:layout_marginRight="5dip"
+                    android:background="@drawable/box_color_white" />
+        
+                <LinearLayout
+                    android:orientation="vertical"
+                    android:layout_height="wrap_content"
+                    android:layout_width="fill_parent"
+                    android:layout_weight="1">
+        
+                    <!-- WHAT -->
+                    <TextView android:id="@+id/title"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:autoLink="all"
+                        android:textStyle="bold"
+                        style="?android:attr/textAppearanceMediumInverse"
+                    />
+
+                    <!-- WHEN -->
+                    <TextView android:id="@+id/when"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="5dip"
+                        android:textStyle="bold"
+                        style="?android:attr/textAppearanceSmallInverse"
+                    />
+            
+                    <!-- TIMEZONE -->
+                    <LinearLayout android:id="@+id/timezone_container"
+                        android:orientation="horizontal"
+                        android:layout_width="fill_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/textAppearanceSmallInverse"
+                        />
+        
+                        <TextView android:id="@+id/timezone"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            android:textStyle="bold"
+                            style="?android:attr/textAppearanceSmallInverse"
+                        />
+                    </LinearLayout>
+                
+                    <!-- REPEATS -->
+                    <LinearLayout android:id="@+id/repeat_container"
+                        android:orientation="horizontal"
+                        android:layout_width="fill_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="fill_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginLeft="3dip"
+                            style="?android:attr/textAppearanceSmallInverse"
+                        />
+                    </LinearLayout>
+        
+                    <!-- WHERE -->
+                    <TextView android:id="@+id/where"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="5dip"
+                        android:autoLink="all"
+                        android:textStyle="bold"
+                        style="?android:attr/textAppearanceSmallInverse"
+                    />
+        
+                    <!-- DESCRIPTION -->
+                    <TextView android:id="@+id/description"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="5dip"
+                        android:autoLink="all"
+                        style="?android:attr/textAppearanceSmallInverse"
+                    />
+            
+                    <!-- CALENDAR -->
+                    <LinearLayout android:id="@+id/calendar_container"
+                        android:orientation="horizontal"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="5dip"
+                    >
+                
+                        <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/textAppearanceSmallInverse"
+                        />
+        
+                        <TextView android:id="@+id/calendar"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            android:textStyle="bold"
+                            style="?android:attr/textAppearanceSmallInverse"
+                        />
+                    </LinearLayout>
+                </LinearLayout>
+            </LinearLayout>
+            
+            <!-- REMINDERS -->
+            <LinearLayout android:id="@+id/reminders_container"
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="5dip"
+                android:paddingRight="5dip"
+                android:paddingTop="5dip"
+                android:paddingBottom="1dip">
+            
+                <TextView android:id="@+id/reminders_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/reminders_label"
+                    style="?android:attr/textAppearanceMedium"/>
+            
+                <LinearLayout android:id="@+id/reminder_items_container"
+                    android:orientation="vertical"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content">
+                </LinearLayout>
+            </LinearLayout>
+            
+            <!-- RESPONSE -->
+            <LinearLayout android:id="@+id/response_container"
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="5dip"
+                android:paddingRight="5dip"
+                android:paddingTop="5dip"
+                android:paddingBottom="1dip">
+            
+                <TextView android:id="@+id/response_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/view_event_response_label"
+                    style="?android:attr/textAppearanceMedium"/>
+            
+                <Spinner android:id="@+id/response_value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/response_labels1"/>
+            </LinearLayout>
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
diff --git a/res/layout/ics_import_activity.xml b/res/layout/ics_import_activity.xml
new file mode 100644 (file)
index 0000000..37427fc
--- /dev/null
@@ -0,0 +1,74 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="@drawable/event_background" >
+
+    <LinearLayout android:id="@+id/import_ics"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+        <TextView android:id="@+id/num_events_label"
+                  android:text="@string/num_events"
+                  android:gravity="right|top"
+                  android:paddingRight="10dip"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content" />
+
+        <TextView android:id="@+id/num_events"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content" />
+
+        <LinearLayout android:id="@+id/calendar_row"
+                      android:orientation="horizontal"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content">
+
+                <ImageView android:id="@+id/calendar_icon"
+                    android:layout_marginTop="4dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/sym_calendar_event" />
+
+                <Spinner android:id="@+id/calendars"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_column="1">
+
+            <Button android:id="@+id/import_button"
+                    android:layout_marginTop="8dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/import_label" />
+
+            <Button android:id="@+id/cancel_button"
+                    android:layout_marginTop="8dip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/cancel_label" />
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/res/layout/month_activity.xml b/res/layout/month_activity.xml
new file mode 100644 (file)
index 0000000..3f287ca
--- /dev/null
@@ -0,0 +1,86 @@
+<?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:id="@+id/month_container"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+     
+    <RelativeLayout 
+        android:layout_width="fill_parent" 
+        android:layout_height="?android:attr/windowTitleSize"
+        style="?android:attr/windowTitleBackgroundStyle">
+
+        <TextView android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center" 
+            style="?android:attr/windowTitleStyle"
+            android:background="@null"
+            android:fadingEdge="horizontal"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+        />
+
+        <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>
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <LinearLayout android:id="@+id/day_names"
+        android:orientation="horizontal"
+        android:background="@drawable/daynames_background"
+        android:layout_width="fill_parent"
+        android:layout_height="23dip" >
+        
+        <TextView android:id="@+id/day0"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day1"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day2"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day3"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day4"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day5"
+            style="@style/MonthView_DayLabel" />
+        <TextView android:id="@+id/day6"
+            style="@style/MonthView_DayLabel" />
+    </LinearLayout>
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <ViewSwitcher android:id="@+id/switcher"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+</LinearLayout>
diff --git a/res/layout/month_bubble.xml b/res/layout/month_bubble.xml
new file mode 100644 (file)
index 0000000..6c6c67a
--- /dev/null
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bubble_layout"
+    android:orientation="vertical"
+    android:paddingTop="5dip"
+    android:paddingLeft="10dip"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <TableLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:shrinkColumns="1">
+
+        <TableRow android:id="@+id/item_layout0">
+            <TextView android:id="@+id/time0"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/event_title0"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textStyle="bold"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+
+        <TableRow android:id="@+id/item_layout1">
+            <TextView android:id="@+id/time1"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/event_title1"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textStyle="bold"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+
+        <TableRow android:id="@+id/item_layout2">
+            <TextView android:id="@+id/time2"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/event_title2"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textStyle="bold"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+
+        <TableRow android:id="@+id/item_layout3">
+            <TextView android:id="@+id/time3"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/event_title3"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textStyle="bold"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+    </TableLayout>
+
+    <TextView android:id="@+id/plus_more"
+        android:singleLine="true"
+        android:textStyle="bold"
+        android:textColor="@drawable/panel_text_foreground"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/res/layout/status_bar_event.xml b/res/layout/status_bar_event.xml
new file mode 100644 (file)
index 0000000..8eb9b2a
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/event_layout"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+    <TableLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:shrinkColumns="1">
+
+        <TableRow>
+            <TextView android:id="@+id/what_label"
+                android:text="@string/what_label"
+                android:gravity="right"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_label_foreground"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/event_title" android:autoLink="all"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+
+        <TableRow>
+            <TextView android:id="@+id/when_label"
+                android:text="@string/when_label"
+                android:gravity="right"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_label_foreground"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+
+                <TextView android:id="@+id/date"
+                    android:textColor="@drawable/panel_text_foreground"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+                <TextView android:id="@+id/time"
+                    android:textColor="@drawable/panel_text_foreground"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            </LinearLayout>
+        </TableRow>
+
+        <TableRow>
+            <TextView android:id="@+id/where_label"
+                android:text="@string/where_label"
+                android:gravity="right"
+                android:paddingRight="10dip"
+                android:textColor="@drawable/panel_label_foreground"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+            <TextView android:id="@+id/where" android:autoLink="all"
+                android:textColor="@drawable/panel_text_foreground"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+        </TableRow>
+    </TableLayout>
+</LinearLayout>
+
+
diff --git a/res/layout/view_reminder_item.xml b/res/layout/view_reminder_item.xml
new file mode 100644 (file)
index 0000000..957396c
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content" >
+    
+    <ImageView android:id="@+id/reminder_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:src="@drawable/ic_alarm_white"
+        android:focusable="false"
+        android:clickable="false"
+    />
+    
+    <TextView android:id="@+id/reminder"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/TextAppearance.EditEvent_Value"
+    />
+</LinearLayout>
diff --git a/res/layout/week_activity.xml b/res/layout/week_activity.xml
new file mode 100644 (file)
index 0000000..aeadd56
--- /dev/null
@@ -0,0 +1,58 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent" >
+     
+    <RelativeLayout 
+        android:layout_width="fill_parent" 
+        android:layout_height="?android:attr/windowTitleSize"
+        style="?android:attr/windowTitleBackgroundStyle">
+
+        <TextView android:id="@+id/title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center" 
+            style="?android:attr/windowTitleStyle"
+            android:background="@null"
+            android:fadingEdge="horizontal"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+        />
+
+        <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>
+
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dip"
+        android:background="@android:drawable/divider_horizontal_dark" />
+
+    <ViewSwitcher android:id="@+id/switcher"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+</LinearLayout>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644 (file)
index 0000000..7c0598c
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Přidat kalendáře"</string>
+  <string name="agenda_view">"Program jednání"</string>
+  <string name="agenda_when_label">Kdy\u2026</string>
+  <string name="agenda_where_label">Kde\u2026</string>
+  <string name="alert_missed_events_multiple">a <xliff:g id="reminder_count">%s</xliff:g> dalších připomenutí</string>
+  <string name="alert_missed_events_single">a <xliff:g id="reminder_count">%s</xliff:g> další připomenutí</string>
+  <string name="alert_when_label">Kdy:</string>
+  <string name="alert_where_label">Kde:</string>
+  <string name="all_day_event">Celodenní událost</string>
+  <string name="app_label">Kalendář</string>
+  <string name="calendars_title">"Kalendáře"</string>
+  <string name="cancel_label">"Storno"</string>
+  <string name="custom">"Vlastní\u2026 (nelze přizpůsobit v zařízení)"</string>
+  <string name="daily">Denně</string>
+  <string name="day_view">"Zobrazení dne"</string>
+  <string name="delete_label">"Odstranit událost"</string>
+  <string name="description_label">Popis</string>
+  <string name="discard_label">Zrušit změny</string>
+  <string name="dismiss_all_label">"Zavřít vše"</string>
+  <string name="dismiss_label">"Zavřít"</string>
+  <string name="does_not_repeat">Neopakovat</string>
+  <string name="done_label">"Hotovo"</string>
+  <string name="edit_event_all_day_label">Celý den</string>
+  <string name="edit_event_calendar_label">Kalendář</string>
+  <string name="edit_event_from_label">Od</string>
+  <string name="edit_event_hide_extra_options">Skrýt doplňkové možnosti</string>
+  <string name="edit_event_show_extra_options">Zobrazit doplňkové možnosti</string>
+  <string name="edit_event_to_label">Do</string>
+  <string name="event_create">"Nová událost"</string>
+  <string name="event_delete">"Odstranit událost"</string>
+  <string name="event_edit">"Upravit událost"</string>
+  <string name="event_edit_title">"Podrobnosti o události"</string>
+  <string name="event_info_title">Zobrazit událost</string>
+  <string name="event_info_title_invite">Pozvánka na schůzku</string>
+  <string name="event_view">"Zobrazit událost"</string>
+  <string name="every_label">"Každý"</string>
+  <string name="every_weekday">"Každý den v týdnu (Po\u2013Pá)"</string>
+  <string name="friday_letter">"Pá"</string>
+  <string name="from_label">Od</string>
+  <string name="goto_today">"Přejít na dnešek"</string>
+  <string name="hint_description">"Popis události"</string>
+  <string name="hint_what">"Název události"</string>
+  <string name="hint_where">"Místo události"</string>
+  <string name="import_label">"Import"</string>
+  <string name="menu_preferences">"Nastavení"</string>
+  <string name="menu_select_calendars">"Správa kalendářů"</string>
+  <string name="modify_all">Změnit všechny události</string>
+  <string name="modify_all_following">Změnit všechny budoucí události</string>
+  <string name="modify_event">Změnit tuto událost</string>
+  <string name="monday_letter">"Po"</string>
+  <string name="month_view">"Zobrazení měsíce"</string>
+  <string name="monthly_on_day">"Měsíčně (<xliff:g id="day_of_month">%s</xliff:g>. den)"</string>
+  <string name="monthly_on_day_count">"Měsíčně (každý <xliff:g id="ordinal_number">%1$s</xliff:g>. <xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Další možnosti"</string>
+  <string name="no_title_label">(Předmět nezadán)</string>
+  <string name="notes_label">Poznámky</string>
+  <string name="num_events">"Čís. události"</string>
+  <string name="on_label">"Zapnuto"</string>
+  <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> dalších \u2026)"</string>
+  <string name="preferences_alerts_sound_title">Vyzv. tón</string>
+  <string name="preferences_alerts_title">Nastavení připomenutí</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Nastavit připomenutí</string>
+  <string name="preferences_alerts_type_title">Výstrahy a oznámení\u2026</string>
+  <string name="preferences_alerts_vibrate_title">Vibrace</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Nastavit výchozí připomenutí</string>
+  <string name="preferences_default_reminder_title">Výchozí připomenutí\u2026</string>
+  <string name="preferences_general_title">Obecné nastavení</string>
+  <string name="preferences_hide_declined_title">Skrýt odmítnuté události</string>
+  <string name="preferences_title">"Nastavení"</string>
+  <string name="presence_label">Účast</string>
+  <string name="privacy_label">Utajení</string>
+  <string name="reminder">Připomenutí</string>
+  <string name="reminders_label">Připomenutí</string>
+  <string name="reminders_remove_label">Odebrat</string>
+  <string name="remove_calendars">"Odebrat kalendáře"</string>
+  <string name="repeat_on_label">"Opakovat dne"</string>
+  <string name="repeats_label">Opakování</string>
+  <string name="saturday_letter">"So"</string>
+  <string name="save_label">Uložit</string>
+  <string name="select_calendars_to_sync">"Vyberte kalendáře k synchronizaci"</string>
+  <string name="set_time">"Nastavit čas"</string>
+  <string name="snooze_all_label">"Připomenout vše znovu"</string>
+  <string name="snooze_label">"Připomenout znovu"</string>
+  <string name="sunday_letter">"Ne"</string>
+  <string name="thursday_letter">"Čt"</string>
+  <string name="to_label">do:</string>
+  <string name="tuesday_letter">"Út"</string>
+  <string name="until">"do"</string>
+  <string name="view_event_accept_button">Ano</string>
+  <string name="view_event_accept_label">Účastní se</string>
+  <string name="view_event_calendar_label">Kalendář</string>
+  <string name="view_event_decline_button">Ne</string>
+  <string name="view_event_decline_label">Neúčastní se</string>
+  <string name="view_event_edit">Upravit</string>
+  <string name="view_event_no_response_label">(Bez odpovědi)</string>
+  <string name="view_event_reminders_label">Připomenutí</string>
+  <string name="view_event_response_label">Odpověď</string>
+  <string name="view_event_tentative_button">Možná</string>
+  <string name="view_event_tentative_label">Možná se účastní</string>
+  <string name="view_label">"Zobrazit"</string>
+  <string name="wednesday_letter">"St"</string>
+  <string name="week_view">"Zobrazení týdne"</string>
+  <string name="weekly">"Týdně (každý <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Co</string>
+  <string name="when_label">Kdy</string>
+  <string name="where_label">Kde</string>
+  <string name="yearly">"Ročně (dne <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-de-rDE/strings.xml b/res/values-de-rDE/strings.xml
new file mode 100644 (file)
index 0000000..a58ee07
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Kalender hinzufügen"</string>
+  <string name="add_new_reminder">Erinnerung hinzufügen</string>
+  <string name="agenda_today">Heute</string>
+  <string name="agenda_view">"Tagesordnung"</string>
+  <string name="agenda_when_label">Wann</string>
+  <string name="agenda_where_label">Wo</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> weitere Erinnerungen)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> weitere Erinnerung)</string>
+  <string name="alert_when_label">Wann:</string>
+  <string name="alert_where_label">Wo:</string>
+  <string name="all_day_event">Ganztägiges Ereignis</string>
+  <string name="app_label">Kalender</string>
+  <string name="calendars_title">"Eigene Kalender"</string>
+  <string name="cancel_label">"Abbrechen"</string>
+  <string name="custom">"Benutzerdefiniert\u2026 (Benutzerdefinierung auf Telefon nicht möglich)"</string>
+  <string name="daily">Täglich</string>
+  <string name="day_view">"Tag"</string>
+  <string name="delete_event_label">"Ereignis löschen"</string>
+  <string name="delete_label">"Löschen"</string>
+  <string name="delete_this_event_title">Dieses Ereignis wird gelöscht.</string>
+  <string name="delete_title">Löschen</string>
+  <string name="description_label">Beschreibung</string>
+  <string name="discard_label">Änderungen verwerfen</string>
+  <string name="dismiss_all_label">"Alle schließen"</string>
+  <string name="dismiss_label">"Schließen"</string>
+  <string name="does_not_repeat">Wird nicht wiederholt</string>
+  <string name="done_label">"Fertig"</string>
+  <string name="edit_event_all_day_label">Ganztägig</string>
+  <string name="edit_event_calendar_label">Kalender</string>
+  <string name="edit_event_from_label">Von</string>
+  <string name="edit_event_hide_extra_options">Zusätzliche Optionen ausblenden</string>
+  <string name="edit_event_label">"Ereignis bearbeiten"</string>
+  <string name="edit_event_show_extra_options">Zusätzliche Optionen anzeigen</string>
+  <string name="edit_event_to_label">An</string>
+  <string name="event_create">"Neues Ereignis"</string>
+  <string name="event_delete">"Ereignis löschen"</string>
+  <string name="event_edit">"Ereignis bearbeiten"</string>
+  <string name="event_edit_title">"Ereignisdetails"</string>
+  <string name="event_info_title">Ereignis anzeigen</string>
+  <string name="event_info_title_invite">Besprechungseinladung</string>
+  <string name="event_view">"Ereignis anzeigen"</string>
+  <string name="every_label">"Alle"</string>
+  <string name="every_weekday">"Jeden Wochentag (Mon\u2013Fre)"</string>
+  <string name="friday_letter">"F"</string>
+  <string name="from_label">Von</string>
+  <string name="goto_today">"Heute"</string>
+  <string name="hint_description">"Ereignisbeschreibung"</string>
+  <string name="hint_what">"Ereignisname"</string>
+  <string name="hint_where">"Ereignisort"</string>
+  <string name="import_label">"Importieren"</string>
+  <string name="menu_preferences">"Einstellungen"</string>
+  <string name="menu_select_calendars">"Eigene Kalender"</string>
+  <string name="modify_all">Alle Ereignisse in der Serie ändern.</string>
+  <string name="modify_all_following">Dieses und alle zukünftigen Ereignisse ändern.</string>
+  <string name="modify_event">Nur dieses Ereignis ändern.</string>
+  <string name="monday_letter">"M"</string>
+  <string name="month_view">"Monat"</string>
+  <string name="monthly_on_day">"Monatlich (am Tag <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Monatlich (jeden <xliff:g id="ordinal_number">%1$s</xliff:g> <xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Weitere Optionen"</string>
+  <string name="no_calendars">Warten auf Synchronsierung</string>
+  <string name="no_calendars_msg">Ihre Ereignisse werden in Kürze erscheinen.</string>
+  <string name="no_title_label">(Kein Betreff)</string>
+  <string name="notes_label">Notizen</string>
+  <string name="num_events">"Anzahl Ereignisse"</string>
+  <string name="ok_label">"OK"</string>
+  <string name="on_label">"Ein"</string>
+  <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> mehr \u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">Klingelton auswählen</string>
+  <string name="preferences_alerts_sound_title">Sound</string>
+  <string name="preferences_alerts_title">Erinnerungseinstellungen</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Erinnerung einstellen</string>
+  <string name="preferences_alerts_type_title">Warnhinweise &amp; Benachrichtigungen einstellen</string>
+  <string name="preferences_alerts_vibrate_title">Vibrieren</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Standarderinnerung einstellen</string>
+  <string name="preferences_default_reminder_title">Standarderinnerung einstellen</string>
+  <string name="preferences_general_title">Kalenderansicht-Einstellung</string>
+  <string name="preferences_hide_declined_title">Abgelehnte Ereignisse ausblenden</string>
+  <string name="preferences_title">"Einstellungen"</string>
+  <string name="presence_label">Anwesenheit</string>
+  <string name="privacy_label">Datenschutz</string>
+  <string name="reminder">Erinnerung</string>
+  <string name="reminders_label">Erinnerungen</string>
+  <string name="reminders_remove_label">Entfernen</string>
+  <string name="remove_calendars">"Kalender entfernen"</string>
+  <string name="repeat_on_label">"Wiederholen ein"</string>
+  <string name="repeats_label">Wiederholt</string>
+  <string name="saturday_letter">"S"</string>
+  <string name="save_label">Speichern</string>
+  <string name="select_calendars_to_sync">"Kalender für Synchr. auswählen"</string>
+  <string name="set_time">"Uhrzeit einstellen"</string>
+  <string name="snooze_all_label">"An alle erinnern"</string>
+  <string name="snooze_label">"Erinnern"</string>
+  <string name="sunday_letter">"S"</string>
+  <string name="thursday_letter">"D"</string>
+  <string name="to_label">bis</string>
+  <string name="tuesday_letter">"D"</string>
+  <string name="until">"Bis"</string>
+  <string name="view_event_accept_button">Ja</string>
+  <string name="view_event_accept_label">Nimmt teil</string>
+  <string name="view_event_calendar_label">Kalender</string>
+  <string name="view_event_decline_button">Nein</string>
+  <string name="view_event_decline_label">Nimmt nicht teil</string>
+  <string name="view_event_edit">Bearbeiten</string>
+  <string name="view_event_no_response_label">(Keine Antwort)</string>
+  <string name="view_event_reminders_label">Erinnern an</string>
+  <string name="view_event_response_label">Teilnehmend?</string>
+  <string name="view_event_tentative_button">Eventuell</string>
+  <string name="view_event_tentative_label">Eventuelle Teilnahme</string>
+  <string name="view_event_timezone_label">Lokale Zeitzone</string>
+  <string name="view_label">"Ansicht"</string>
+  <string name="wednesday_letter">"M"</string>
+  <string name="week_view">"Woche"</string>
+  <string name="weekly">"Wöchentlich (alle <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Was</string>
+  <string name="when_label">Wann</string>
+  <string name="where_label">Wo</string>
+  <string name="yearly">"Jährlich (am <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644 (file)
index 0000000..f206c4f
--- /dev/null
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Add calendars"</string>
+  <string name="add_new_reminder">Add reminder</string>
+  <string name="agenda_today">Today</string>
+  <string name="agenda_view">"Agenda"</string>
+  <string name="agenda_when_label">When</string>
+  <string name="agenda_where_label">Where</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> more reminders)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> more reminder)</string>
+  <string name="alert_when_label">When:</string>
+  <string name="alert_where_label">Where:</string>
+  <string name="all_day_event">All-day event</string>
+  <string name="app_label">Calendar</string>
+  <string name="calendars_title">"My calendars"</string>
+  <string name="cancel_label">"Cancel"</string>
+  <string name="custom">"Custom\u2026 (cannot customise on phone)"</string>
+  <string name="daily">Daily</string>
+  <string name="day_view">"Day"</string>
+  <string name="delete_event_label">"Delete event"</string>
+  <string name="delete_label">"Delete"</string>
+  <string name="delete_this_event_title">This event will be deleted.</string>
+  <string name="delete_title">Delete</string>
+  <string name="description_label">Description</string>
+  <string name="discard_label">Discard changes</string>
+  <string name="dismiss_all_label">"Dismiss all"</string>
+  <string name="dismiss_label">"Dismiss"</string>
+  <string name="does_not_repeat">Does not repeat</string>
+  <string name="done_label">"Done"</string>
+  <string name="edit_event_all_day_label">All day</string>
+  <string name="edit_event_calendar_label">Calendar</string>
+  <string name="edit_event_from_label">From</string>
+  <string name="edit_event_hide_extra_options">Hide extra options</string>
+  <string name="edit_event_label">"Edit event"</string>
+  <string name="edit_event_show_extra_options">Show extra options</string>
+  <string name="edit_event_to_label">To</string>
+  <string name="event_create">"New event"</string>
+  <string name="event_delete">"Delete event"</string>
+  <string name="event_edit">"Edit event"</string>
+  <string name="event_edit_title">"Event details"</string>
+  <string name="event_info_title">View event</string>
+  <string name="event_info_title_invite">Meeting invitation</string>
+  <string name="event_view">"View event"</string>
+  <string name="every_label">"Every"</string>
+  <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+  <string name="friday_letter">"F"</string>
+  <string name="from_label">From</string>
+  <string name="goto_today">"Today"</string>
+  <string name="hint_description">"Event description"</string>
+  <string name="hint_what">"Event name"</string>
+  <string name="hint_where">"Event location"</string>
+  <string name="import_label">"Import"</string>
+  <string name="menu_preferences">"Settings"</string>
+  <string name="menu_select_calendars">"My calendars"</string>
+  <string name="modify_all">Change all events in the series.</string>
+  <string name="modify_all_following">Change this and all future events.</string>
+  <string name="modify_event">Change only this event.</string>
+  <string name="monday_letter">"M"</string>
+  <string name="month_view">"Month"</string>
+  <string name="monthly_on_day">"Monthly (on day <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Monthly (every <xliff:g id="ordinal_number">%1$s</xliff:g> <xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"More options"</string>
+  <string name="no_title_label">(No subject)</string>
+  <string name="notes_label">Notes</string>
+  <string name="num_events">"Num events"</string>
+  <string name="ok_label">"OK"</string>
+  <string name="on_label">"On"</string>
+  <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> more \u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">Select ringtone</string>
+  <string name="preferences_alerts_sound_title">Sound</string>
+  <string name="preferences_alerts_title">Reminder settings</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Set reminder</string>
+  <string name="preferences_alerts_type_title">Set alerts &amp; notifications</string>
+  <string name="preferences_alerts_vibrate_title">Vibrate</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Set default reminder</string>
+  <string name="preferences_default_reminder_title">Set default reminder</string>
+  <string name="preferences_general_title">Calendar view setting</string>
+  <string name="preferences_hide_declined_title">Hide declined events</string>
+  <string name="preferences_title">"Settings"</string>
+  <string name="presence_label">Presence</string>
+  <string name="privacy_label">Privacy</string>
+  <string name="reminder">Reminder</string>
+  <string name="reminders_label">Reminders</string>
+  <string name="reminders_remove_label">Remove</string>
+  <string name="remove_calendars">"Remove calendars"</string>
+  <string name="repeat_on_label">"Repeat on"</string>
+  <string name="repeats_label">Repeats</string>
+  <string name="saturday_letter">"S"</string>
+  <string name="save_label">Save</string>
+  <string name="select_calendars_to_sync">"Select calendars to sync"</string>
+  <string name="set_time">"Set the time"</string>
+  <string name="snooze_all_label">"Snooze all"</string>
+  <string name="snooze_label">"Snooze"</string>
+  <string name="sunday_letter">"S"</string>
+  <string name="thursday_letter">"T"</string>
+  <string name="to_label">to</string>
+  <string name="tuesday_letter">"T"</string>
+  <string name="until">"Until"</string>
+  <string name="view_event_accept_button">Yes</string>
+  <string name="view_event_accept_label">Attending</string>
+  <string name="view_event_calendar_label">Calendar</string>
+  <string name="view_event_decline_button">No</string>
+  <string name="view_event_decline_label">Not attending</string>
+  <string name="view_event_edit">Edit</string>
+  <string name="view_event_no_response_label">(No response)</string>
+  <string name="view_event_reminders_label">Reminders</string>
+  <string name="view_event_response_label">Attending?</string>
+  <string name="view_event_tentative_button">Maybe</string>
+  <string name="view_event_tentative_label">Maybe attending</string>
+  <string name="view_event_timezone_label">Local time zone</string>
+  <string name="view_label">"View"</string>
+  <string name="wednesday_letter">"W"</string>
+  <string name="week_view">"Week"</string>
+  <string name="weekly">"Weekly (every <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">What</string>
+  <string name="when_label">When</string>
+  <string name="where_label">Where</string>
+  <string name="yearly">"Yearly (on <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644 (file)
index 0000000..8dde2b9
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Agregar calendarios"</string>
+  <string name="add_new_reminder">Agregar aviso</string>
+  <string name="agenda_today">Hoy</string>
+  <string name="agenda_view">"Agenda"</string>
+  <string name="agenda_when_label">Cuándo</string>
+  <string name="agenda_where_label">Dónde</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> más avisos)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> más avisos)</string>
+  <string name="alert_when_label">Cuándo:</string>
+  <string name="alert_where_label">Dónde:</string>
+  <string name="all_day_event">Evento de todo el día</string>
+  <string name="app_label">Calendario</string>
+  <string name="calendars_title">"Mis calendarios"</string>
+  <string name="cancel_label">"Cancelar"</string>
+  <string name="custom">"Personalizar\u2026 (no se puede personalizar en teléfono)"</string>
+  <string name="daily">Diariamente</string>
+  <string name="day_view">"Día"</string>
+  <string name="delete_event_label">"Eliminar evento"</string>
+  <string name="delete_label">"Eliminar"</string>
+  <string name="delete_this_event_title">Se eliminará este evento:</string>
+  <string name="delete_title">Eliminar</string>
+  <string name="description_label">Descripción</string>
+  <string name="discard_label">Rechazar cambios</string>
+  <string name="dismiss_all_label">"Rechazar todo"</string>
+  <string name="dismiss_label">"Descartar"</string>
+  <string name="does_not_repeat">No se repite</string>
+  <string name="done_label">"Listo"</string>
+  <string name="edit_event_all_day_label">Todo el día</string>
+  <string name="edit_event_calendar_label">Calendario</string>
+  <string name="edit_event_from_label">De</string>
+  <string name="edit_event_hide_extra_options">Ocultar opciones extras</string>
+  <string name="edit_event_label">"Editar evento"</string>
+  <string name="edit_event_show_extra_options">Mostrar opciones extras</string>
+  <string name="edit_event_to_label">Para</string>
+  <string name="event_create">"Nuevo evento"</string>
+  <string name="event_delete">"Eliminar evento"</string>
+  <string name="event_edit">"Editar evento"</string>
+  <string name="event_edit_title">"Detalles del evento"</string>
+  <string name="event_info_title">Ver evento</string>
+  <string name="event_info_title_invite">Invitación a reunión</string>
+  <string name="event_view">"Ver evento"</string>
+  <string name="every_label">"Cada"</string>
+  <string name="every_weekday">"Todos los días laborables (Lun\u2013Vie)"</string>
+  <string name="friday_letter">"V"</string>
+  <string name="from_label">De</string>
+  <string name="goto_today">"Hoy"</string>
+  <string name="hint_description">"Descripción del evento"</string>
+  <string name="hint_what">"Nombre del evento"</string>
+  <string name="hint_where">"Ubicación del evento"</string>
+  <string name="import_label">"Importar"</string>
+  <string name="menu_preferences">"Configuración"</string>
+  <string name="menu_select_calendars">"Mis calendarios"</string>
+  <string name="modify_all">Cambiar todos los eventos de la serie.</string>
+  <string name="modify_all_following">Cambiar esto y todos los eventos futuros. </string>
+  <string name="modify_event">Cambiar sólo este evento.</string>
+  <string name="monday_letter">"L"</string>
+  <string name="month_view">"Mes"</string>
+  <string name="monthly_on_day">"Mensual (en día <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Mensual (cada <xliff:g id="ordinal_number">%1$s</xliff:g><xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Más opciones"</string>
+  <string name="no_calendars">Esperando sincronización</string>
+  <string name="no_calendars_msg">Sus eventos aparecerán en breve.</string>
+  <string name="no_title_label">(Sin asunto)</string>
+  <string name="notes_label">Notas</string>
+  <string name="num_events">"Nº de eventos"</string>
+  <string name="ok_label">"Aceptar"</string>
+  <string name="on_label">"Activado"</string>
+  <string name="plus_N_more">"(además de <xliff:g id="more_count">%d</xliff:g> más\u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">Seleccionar tono de timbre</string>
+  <string name="preferences_alerts_sound_title">Sonido</string>
+  <string name="preferences_alerts_title">Configuración de aviso</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Configurar aviso</string>
+  <string name="preferences_alerts_type_title">Configurar alertas y notificaciones</string>
+  <string name="preferences_alerts_vibrate_title">Vibrar</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Configurar aviso predeterminado</string>
+  <string name="preferences_default_reminder_title">Configurar aviso predeterminado</string>
+  <string name="preferences_general_title">Configuración de visualización del calendario</string>
+  <string name="preferences_hide_declined_title">Ocultar eventos rechazados</string>
+  <string name="preferences_title">"Configuración"</string>
+  <string name="presence_label">Presencia</string>
+  <string name="privacy_label">Privacidad</string>
+  <string name="reminder">Aviso</string>
+  <string name="reminders_label">Avisos</string>
+  <string name="reminders_remove_label">Quitar</string>
+  <string name="remove_calendars">"Quitar calendarios"</string>
+  <string name="repeat_on_label">"Repetir en"</string>
+  <string name="repeats_label">Se repite</string>
+  <string name="saturday_letter">"S"</string>
+  <string name="save_label">Guardar</string>
+  <string name="select_calendars_to_sync">"Seleccionar calendarios a sincronizar"</string>
+  <string name="set_time">"Configurar la hora"</string>
+  <string name="snooze_all_label">"Posponer todo"</string>
+  <string name="snooze_label">"Posponer"</string>
+  <string name="sunday_letter">"D"</string>
+  <string name="thursday_letter">"J"</string>
+  <string name="to_label">para</string>
+  <string name="tuesday_letter">"M"</string>
+  <string name="until">"Hasta"</string>
+  <string name="view_event_accept_button">Sí</string>
+  <string name="view_event_accept_label">Asistes</string>
+  <string name="view_event_calendar_label">Calendario</string>
+  <string name="view_event_decline_button">No</string>
+  <string name="view_event_decline_label">No asistes</string>
+  <string name="view_event_edit">Editar</string>
+  <string name="view_event_no_response_label">(Sin respuesta)</string>
+  <string name="view_event_reminders_label">Avisos</string>
+  <string name="view_event_response_label">¿Asistes?</string>
+  <string name="view_event_tentative_button">Quizás</string>
+  <string name="view_event_tentative_label">Quizás asista</string>
+  <string name="view_event_timezone_label">Zona horaria local</string>
+  <string name="view_label">"Ver"</string>
+  <string name="wednesday_letter">"M"</string>
+  <string name="week_view">"Semana"</string>
+  <string name="weekly">"Semanal (cada <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Qué</string>
+  <string name="when_label">Cuándo</string>
+  <string name="where_label">Dónde</string>
+  <string name="yearly">"Anual (en <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-fr-rFR/strings.xml b/res/values-fr-rFR/strings.xml
new file mode 100644 (file)
index 0000000..bf4fa0b
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Ajouter agendas"</string>
+  <string name="add_new_reminder">Ajouter rappel</string>
+  <string name="agenda_today">Aujourd\'hui</string>
+  <string name="agenda_view">"Agenda"</string>
+  <string name="agenda_when_label">Quand</string>
+  <string name="agenda_where_label">Où</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> plus de rappels)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> plus de rappel)</string>
+  <string name="alert_when_label">Quand :</string>
+  <string name="alert_where_label">Où :</string>
+  <string name="all_day_event">Journée entière</string>
+  <string name="app_label">Agenda</string>
+  <string name="calendars_title">"Mes agendas"</string>
+  <string name="cancel_label">"Annuler"</string>
+  <string name="custom">"Personnaliser\u2026 (impossible de personnaliser sur le téléphone)"</string>
+  <string name="daily">Quotidien</string>
+  <string name="day_view">"Jour"</string>
+  <string name="delete_event_label">"Supprimer l\'événement"</string>
+  <string name="delete_label">"Supprimer"</string>
+  <string name="delete_this_event_title">Cet événement sera supprimé.</string>
+  <string name="delete_title">Supprimer</string>
+  <string name="description_label">Description</string>
+  <string name="discard_label">Abandonner les modifications</string>
+  <string name="dismiss_all_label">"Abandonner tout"</string>
+  <string name="dismiss_label">"Abandonner"</string>
+  <string name="does_not_repeat">Ne se répète pas</string>
+  <string name="done_label">"Terminé"</string>
+  <string name="edit_event_all_day_label">Journée entière</string>
+  <string name="edit_event_calendar_label">Agenda</string>
+  <string name="edit_event_from_label">De</string>
+  <string name="edit_event_hide_extra_options">Masquer les options supplémentaires</string>
+  <string name="edit_event_label">"Modifier l\'événement"</string>
+  <string name="edit_event_show_extra_options">Afficher les options supplémentaires</string>
+  <string name="edit_event_to_label">À</string>
+  <string name="event_create">"Nouvel événement"</string>
+  <string name="event_delete">"Supprimer l\'événement"</string>
+  <string name="event_edit">"Modifier l\'événement"</string>
+  <string name="event_edit_title">"Détails de l\'événement"</string>
+  <string name="event_info_title">Afficher l\'événement</string>
+  <string name="event_info_title_invite">Invitation à une réunion</string>
+  <string name="event_view">"Afficher l\'événement"</string>
+  <string name="every_label">"Chaque"</string>
+  <string name="every_weekday">"Chaque jour ouvré (Lun\u2013Ven)"</string>
+  <string name="friday_letter">"V"</string>
+  <string name="from_label">De</string>
+  <string name="goto_today">"Aujourd\'hui"</string>
+  <string name="hint_description">"Description de l\'événement"</string>
+  <string name="hint_what">"Nom de l\'événement"</string>
+  <string name="hint_where">"Lieu de l\'événement"</string>
+  <string name="import_label">"Importer"</string>
+  <string name="menu_preferences">"Paramètres"</string>
+  <string name="menu_select_calendars">"Mes agendas"</string>
+  <string name="modify_all">Modifier tous les événements de la série.</string>
+  <string name="modify_all_following">Modifier ceci et les événements futurs.</string>
+  <string name="modify_event">Modifier seulement cet événement.</string>
+  <string name="monday_letter">"L"</string>
+  <string name="month_view">"Mois"</string>
+  <string name="monthly_on_day">"Mensuel (le jour <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Mensuel (chaque <xliff:g id="ordinal_number">%1$s</xliff:g><xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Plus d\'options"</string>
+  <string name="no_calendars">Attente de synchronisation</string>
+  <string name="no_calendars_msg">Vos événements apparaîtront bientôt.</string>
+  <string name="no_title_label">(Aucun objet)</string>
+  <string name="notes_label">Notes</string>
+  <string name="num_events">"Événement num"</string>
+  <string name="ok_label">"OK"</string>
+  <string name="on_label">"Activé"</string>
+  <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> de plus \u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">Sélectionner la sonnerie</string>
+  <string name="preferences_alerts_sound_title">Son</string>
+  <string name="preferences_alerts_title">Paramètres de rappel</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Définir le rappel</string>
+  <string name="preferences_alerts_type_title">Définir les alertes et notifications</string>
+  <string name="preferences_alerts_vibrate_title">Vibreur</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Définir le rappel par défaut</string>
+  <string name="preferences_default_reminder_title">Définir le rappel par défaut</string>
+  <string name="preferences_general_title">Paramètre de vue de l\'agenda</string>
+  <string name="preferences_hide_declined_title">Masquer les événements refusés</string>
+  <string name="preferences_title">"Paramètres"</string>
+  <string name="presence_label">Présence</string>
+  <string name="privacy_label">Vie privée</string>
+  <string name="reminder">Rappel</string>
+  <string name="reminders_label">Rappels</string>
+  <string name="reminders_remove_label">Supprimer</string>
+  <string name="remove_calendars">"Supprimer agendas"</string>
+  <string name="repeat_on_label">"Répéter le"</string>
+  <string name="repeats_label">Répétitions</string>
+  <string name="saturday_letter">"S"</string>
+  <string name="save_label">Enregistrer</string>
+  <string name="select_calendars_to_sync">"Sélectionner les agendas à synchroniser"</string>
+  <string name="set_time">"Définir l\'heure"</string>
+  <string name="snooze_all_label">"Répéter tout"</string>
+  <string name="snooze_label">"Répétition"</string>
+  <string name="sunday_letter">"D"</string>
+  <string name="thursday_letter">"J"</string>
+  <string name="to_label">à</string>
+  <string name="tuesday_letter">"M"</string>
+  <string name="until">"Jusqu\'à"</string>
+  <string name="view_event_accept_button">Oui</string>
+  <string name="view_event_accept_label">Participe</string>
+  <string name="view_event_calendar_label">Agenda</string>
+  <string name="view_event_decline_button">Non</string>
+  <string name="view_event_decline_label">Ne participe pas</string>
+  <string name="view_event_edit">Modifier</string>
+  <string name="view_event_no_response_label">(Pas de réponse)</string>
+  <string name="view_event_reminders_label">Rappels</string>
+  <string name="view_event_response_label">Participe ?</string>
+  <string name="view_event_tentative_button">Peut-être</string>
+  <string name="view_event_tentative_label">Participe peut-être</string>
+  <string name="view_event_timezone_label">Fuseau horaire local</string>
+  <string name="view_label">"Afficher"</string>
+  <string name="wednesday_letter">"M"</string>
+  <string name="week_view">"Semaine"</string>
+  <string name="weekly">"Hebdomadaire (chaque <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Quoi</string>
+  <string name="when_label">Quand</string>
+  <string name="where_label">Où</string>
+  <string name="yearly">"Annuellement (le <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-it-rIT/strings.xml b/res/values-it-rIT/strings.xml
new file mode 100644 (file)
index 0000000..a7c46cb
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Aggiungi calendari"</string>
+  <string name="add_new_reminder">Aggiungi promemoria</string>
+  <string name="agenda_today">Oggi</string>
+  <string name="agenda_view">"Agenda"</string>
+  <string name="agenda_when_label">Quando</string>
+  <string name="agenda_where_label">Dove</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> ulteriori promemoria)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> ulteriore promemoria)</string>
+  <string name="alert_when_label">Quando:</string>
+  <string name="alert_where_label">Dove:</string>
+  <string name="all_day_event">Evento giornata intera</string>
+  <string name="app_label">Calendario</string>
+  <string name="calendars_title">"Calendari"</string>
+  <string name="cancel_label">"Annulla"</string>
+  <string name="custom">"Personalizza\u2026 (impossibile personalizzare sul telefono)"</string>
+  <string name="daily">Giornaliero</string>
+  <string name="day_view">"Giorno"</string>
+  <string name="delete_event_label">"Elimina evento"</string>
+  <string name="delete_label">"Elimina"</string>
+  <string name="delete_this_event_title">L'evento verrà eliminato.</string>
+  <string name="delete_title">Elimina</string>
+  <string name="description_label">Descrizione</string>
+  <string name="discard_label">Ignora modifiche</string>
+  <string name="dismiss_all_label">"Elimina tutti"</string>
+  <string name="dismiss_label">"Elimina"</string>
+  <string name="does_not_repeat">Non si ripete</string>
+  <string name="done_label">"Completato"</string>
+  <string name="edit_event_all_day_label">Giornata intera</string>
+  <string name="edit_event_calendar_label">Calendario</string>
+  <string name="edit_event_from_label">Da</string>
+  <string name="edit_event_hide_extra_options">Nascondi opzioni extra</string>
+  <string name="edit_event_label">"Modifica evento"</string>
+  <string name="edit_event_show_extra_options">Mostra opzioni extra</string>
+  <string name="edit_event_to_label">A</string>
+  <string name="event_create">"Nuovo evento"</string>
+  <string name="event_delete">"Elimina evento"</string>
+  <string name="event_edit">"Modifica evento"</string>
+  <string name="event_edit_title">"Dettagli evento"</string>
+  <string name="event_info_title">Visualizza evento</string>
+  <string name="event_info_title_invite">Invito a Riunione</string>
+  <string name="event_view">"Visualizza evento"</string>
+  <string name="every_label">"Ogni"</string>
+  <string name="every_weekday">"Ogni settimana (Lun\u2013Ven)"</string>
+  <string name="friday_letter">"V"</string>
+  <string name="from_label">Da</string>
+  <string name="goto_today">"Oggi"</string>
+  <string name="hint_description">"Descrizione evento"</string>
+  <string name="hint_what">"Nome evento"</string>
+  <string name="hint_where">"Ubicazione evento"</string>
+  <string name="import_label">"Importa"</string>
+  <string name="menu_preferences">"Impostazioni"</string>
+  <string name="menu_select_calendars">"Calendari"</string>
+  <string name="modify_all">Cambia tutti gli eventi nella serie.</string>
+  <string name="modify_all_following">Cambia questo evento e tutti gli eventi futuri.</string>
+  <string name="modify_event">Cambia solo questo evento.</string>
+  <string name="monday_letter">"L"</string>
+  <string name="month_view">"Mese"</string>
+  <string name="monthly_on_day">"Mensile (il giorno <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Mensile (ogni <xliff:g id="ordinal_number">%1$s</xliff:g><xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Altre opzioni"</string>
+  <string name="no_calendars">In attesa della sincronizzazione</string>
+  <string name="no_calendars_msg">Gli eventi verranno visualizzati tra alcuni istanti.</string>
+  <string name="no_title_label">(Nessun oggetto)</string>
+  <string name="notes_label">Note</string>
+  <string name="num_events">"Num eventi"</string>
+  <string name="ok_label">"OK"</string>
+  <string name="on_label">"On"</string>
+  <string name="plus_N_more">"(più ulteriore <xliff:g id="more_count">%d</xliff:g> \u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">Seleziona suoneria</string>
+  <string name="preferences_alerts_sound_title">Suono</string>
+  <string name="preferences_alerts_title">Impostazioni promemoria</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Imposta promemoria</string>
+  <string name="preferences_alerts_type_title">Imposta avvisi e notifiche</string>
+  <string name="preferences_alerts_vibrate_title">Vibrazione</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Imposta promemoria predefinito</string>
+  <string name="preferences_default_reminder_title">Imposta promemoria predefinito</string>
+  <string name="preferences_general_title">Impostazione visualizzazione calendario</string>
+  <string name="preferences_hide_declined_title">Nascondi eventi rifiutati</string>
+  <string name="preferences_title">"Impostazioni"</string>
+  <string name="presence_label">Presenza</string>
+  <string name="privacy_label">Privacy</string>
+  <string name="reminder">Promemoria</string>
+  <string name="reminders_label">Promemoria</string>
+  <string name="reminders_remove_label">Rimuovi</string>
+  <string name="remove_calendars">"Rimuovi calendari"</string>
+  <string name="repeat_on_label">"Ripeti il"</string>
+  <string name="repeats_label">Ripetizioni</string>
+  <string name="saturday_letter">"S"</string>
+  <string name="save_label">Salva</string>
+  <string name="select_calendars_to_sync">"Selezionare i calendari da sincronizzare"</string>
+  <string name="set_time">"Imposta l'ora"</string>
+  <string name="snooze_all_label">"Posponi tutto"</string>
+  <string name="snooze_label">"Posponi"</string>
+  <string name="sunday_letter">"D"</string>
+  <string name="thursday_letter">"G"</string>
+  <string name="to_label">in</string>
+  <string name="tuesday_letter">"M"</string>
+  <string name="until">"Fino"</string>
+  <string name="view_event_accept_button">Sì</string>
+  <string name="view_event_accept_label">Partecipante</string>
+  <string name="view_event_calendar_label">Calendario</string>
+  <string name="view_event_decline_button">No</string>
+  <string name="view_event_decline_label">Non partecipante</string>
+  <string name="view_event_edit">Modifica</string>
+  <string name="view_event_no_response_label">(Nessuna risposta)</string>
+  <string name="view_event_reminders_label">Promemoria</string>
+  <string name="view_event_response_label">Partecipante?</string>
+  <string name="view_event_tentative_button">Forse</string>
+  <string name="view_event_tentative_label">Probabile partecipante</string>
+  <string name="view_event_timezone_label">Fuso orario locale</string>
+  <string name="view_label">"Visualizza"</string>
+  <string name="wednesday_letter">"M"</string>
+  <string name="week_view">"Settimana"</string>
+  <string name="weekly">"Settimanale (ogni <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Cosa</string>
+  <string name="when_label">Quando</string>
+  <string name="where_label">Dove</string>
+  <string name="yearly">"Annualmente (il <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-land/integers.xml b/res/values-land/integers.xml
new file mode 100644 (file)
index 0000000..9ece108
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <integer name="number_of_hours">8</integer>
+</resources>
diff --git a/res/values-nl-rNL/strings.xml b/res/values-nl-rNL/strings.xml
new file mode 100644 (file)
index 0000000..5d11998
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"Agenda's toevoegen"</string>
+  <string name="agenda_view">"Agendaweergave"</string>
+  <string name="agenda_when_label">Wanneer\u2026</string>
+  <string name="agenda_where_label">Waar\u2026</string>
+  <string name="alert_missed_events_multiple">en <xliff:g id="reminder_count">%s</xliff:g> andere herinneringen</string>
+  <string name="alert_missed_events_single">en <xliff:g id="reminder_count">%s</xliff:g> andere herinnering</string>
+  <string name="alert_when_label">Wanneer:</string>
+  <string name="alert_where_label">Locatie:</string>
+  <string name="all_day_event">Duurt hele dag</string>
+  <string name="app_label">Agenda</string>
+  <string name="calendars_title">"Agenda's"</string>
+  <string name="cancel_label">"Annuleren"</string>
+  <string name="custom">"Aangepast\u2026 (kan niet aanpassen op toestel)"</string>
+  <string name="daily">Elke dag</string>
+  <string name="day_view">"Dagweergave"</string>
+  <string name="delete_label">"Gebeurtenis verwijderen"</string>
+  <string name="description_label">Beschrijving</string>
+  <string name="discard_label">Wijzigingen negeren</string>
+  <string name="dismiss_all_label">"Alles negeren"</string>
+  <string name="dismiss_label">"Negeren"</string>
+  <string name="does_not_repeat">Wordt niet herhaald</string>
+  <string name="done_label">"Gereed"</string>
+  <string name="edit_event_all_day_label">De hele dag</string>
+  <string name="edit_event_calendar_label">Agenda</string>
+  <string name="edit_event_from_label">Van</string>
+  <string name="edit_event_hide_extra_options">Extra opties verbergen</string>
+  <string name="edit_event_show_extra_options">Extra opties weergeven</string>
+  <string name="edit_event_to_label">tot</string>
+  <string name="event_create">"Nieuwe gebeurtenis"</string>
+  <string name="event_delete">"Gebeurtenis verwijderen"</string>
+  <string name="event_edit">"Gebeurtenis"</string>
+  <string name="event_edit_title">"Gebeurtenisdetails"</string>
+  <string name="event_info_title">Gebeurtenis weergeven</string>
+  <string name="event_info_title_invite">Uitnodiging voor afspraak</string>
+  <string name="event_view">"Gebeurtenis weergeven"</string>
+  <string name="every_label">"Elke"</string>
+  <string name="every_weekday">"Elke werkdag (Maa\u2013vri)"</string>
+  <string name="friday_letter">"V"</string>
+  <string name="from_label">Van</string>
+  <string name="goto_today">"Ga naar vandaag"</string>
+  <string name="hint_description">"Gebeurtenisbeschrijving"</string>
+  <string name="hint_what">"Gebeurtenisnaam"</string>
+  <string name="hint_where">"Gebeurtenislocatie"</string>
+  <string name="import_label">"Importeren"</string>
+  <string name="menu_preferences">"Instellingen"</string>
+  <string name="menu_select_calendars">"Agenda's beheren"</string>
+  <string name="modify_all">Alle gebeurtenissen veranderen</string>
+  <string name="modify_all_following">Alle toekomstige gebeurtenissen veranderen</string>
+  <string name="modify_event">Deze gebeurtenis veranderen</string>
+  <string name="monday_letter">"M"</string>
+  <string name="month_view">"Maandweergave"</string>
+  <string name="monthly_on_day">"Elke mnd (op dag <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+  <string name="monthly_on_day_count">"Elke mnd (elke <xliff:g id="ordinal_number">%1$s</xliff:g> <xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"Meer opties"</string>
+  <string name="no_title_label">(Geen onderwerp)</string>
+  <string name="notes_label">Opmerkingen</string>
+  <string name="num_events">"Aant gebeurtenissen"</string>
+  <string name="on_label">"Aan"</string>
+  <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> extra \u2026)"</string>
+  <string name="preferences_alerts_sound_title">Beltoon</string>
+  <string name="preferences_alerts_title">Herinneringsinstellingen</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">Herinnering instellen</string>
+  <string name="preferences_alerts_type_title">Alarmsignalen &amp; meldingen\u2026</string>
+  <string name="preferences_alerts_vibrate_title">Trillen</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">Standaardherinnering instellen</string>
+  <string name="preferences_default_reminder_title">Standaardherinnering\u2026</string>
+  <string name="preferences_general_title">Algemene instellingen</string>
+  <string name="preferences_hide_declined_title">Afgewezen gebeurtenissen verbergen</string>
+  <string name="preferences_title">"Instellingen"</string>
+  <string name="presence_label">Aanwezigheid</string>
+  <string name="privacy_label">Privacy</string>
+  <string name="reminder">Herinnering</string>
+  <string name="reminders_label">Herinneringen</string>
+  <string name="reminders_remove_label">Verwijderen</string>
+  <string name="remove_calendars">"Agenda's verwijderen"</string>
+  <string name="repeat_on_label">"Herhalen op"</string>
+  <string name="repeats_label">Herhaalt</string>
+  <string name="saturday_letter">"Z"</string>
+  <string name="save_label">Opslaan</string>
+  <string name="select_calendars_to_sync">"Agenda's voor synchronisatie selecteren"</string>
+  <string name="set_time">"De tijd instellen"</string>
+  <string name="snooze_all_label">"Alles uitstellen"</string>
+  <string name="snooze_label">"Uitstellen"</string>
+  <string name="sunday_letter">"Z"</string>
+  <string name="thursday_letter">"D"</string>
+  <string name="to_label">tot</string>
+  <string name="tuesday_letter">"D"</string>
+  <string name="until">"Totdat"</string>
+  <string name="view_event_accept_button">Ja</string>
+  <string name="view_event_accept_label">Aanwezig</string>
+  <string name="view_event_calendar_label">Agenda</string>
+  <string name="view_event_decline_button">Nee</string>
+  <string name="view_event_decline_label">Niet aanwezig</string>
+  <string name="view_event_edit">Bewerken</string>
+  <string name="view_event_no_response_label">(Geen reactie)</string>
+  <string name="view_event_reminders_label">Herinneringen</string>
+  <string name="view_event_response_label">Antwoord</string>
+  <string name="view_event_tentative_button">Misschien</string>
+  <string name="view_event_tentative_label">Misschien aanwezig</string>
+  <string name="view_label">"Weergeven"</string>
+  <string name="wednesday_letter">"W"</string>
+  <string name="week_view">"Weekweergave"</string>
+  <string name="weekly">"Wekelijks (elke <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">Wat</string>
+  <string name="when_label">Wanneer</string>
+  <string name="where_label">Locatie</string>
+  <string name="yearly">"Elk jaar (op <xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644 (file)
index 0000000..7c8393b
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string name="add_calendars">"新增行事曆"</string>
+  <string name="add_new_reminder">新增提醒</string>
+  <string name="agenda_today">今天</string>
+  <string name="agenda_view">"議程"</string>
+  <string name="agenda_when_label">時間</string>
+  <string name="agenda_where_label">地點</string>
+  <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> 個其它的提醒)</string>
+  <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> 個其它的提醒)</string>
+  <string name="alert_when_label">時間:</string>
+  <string name="alert_where_label">地點:</string>
+  <string name="all_day_event">全天活動</string>
+  <string name="app_label">行事曆</string>
+  <string name="calendars_title">"我的行事曆"</string>
+  <string name="cancel_label">"取消"</string>
+  <string name="custom">"自訂\u2026 (無法在電話上自訂)"</string>
+  <string name="daily">每天</string>
+  <string name="day_view">"天"</string>
+  <string name="delete_event_label">"刪除活動"</string>
+  <string name="delete_label">"刪除"</string>
+  <string name="delete_this_event_title">將會刪除此活動。</string>
+  <string name="delete_title">刪除</string>
+  <string name="description_label">說明</string>
+  <string name="discard_label">放棄變更</string>
+  <string name="dismiss_all_label">"全部關閉"</string>
+  <string name="dismiss_label">"關閉"</string>
+  <string name="does_not_repeat">不重複</string>
+  <string name="done_label">"完成"</string>
+  <string name="edit_event_all_day_label">全天</string>
+  <string name="edit_event_calendar_label">行事曆</string>
+  <string name="edit_event_from_label">從</string>
+  <string name="edit_event_hide_extra_options">隱藏其它選項</string>
+  <string name="edit_event_label">"編輯活動"</string>
+  <string name="edit_event_show_extra_options">顯示其它選項</string>
+  <string name="edit_event_to_label">到</string>
+  <string name="event_create">"新增活動"</string>
+  <string name="event_delete">"刪除活動"</string>
+  <string name="event_edit">"編輯活動"</string>
+  <string name="event_edit_title">"活動詳細資料"</string>
+  <string name="event_info_title">檢視活動</string>
+  <string name="event_info_title_invite">會議邀請</string>
+  <string name="event_view">"檢視活動"</string>
+  <string name="every_label">"每"</string>
+  <string name="every_weekday">"每個工作日 (星期一\u2013星期五)"</string>
+  <string name="friday_letter">"五"</string>
+  <string name="from_label">從</string>
+  <string name="goto_today">"今天"</string>
+  <string name="hint_description">"活動說明"</string>
+  <string name="hint_what">"活動名稱"</string>
+  <string name="hint_where">"活動地點"</string>
+  <string name="import_label">"匯入"</string>
+  <string name="menu_preferences">"設定"</string>
+  <string name="menu_select_calendars">"我的行事曆"</string>
+  <string name="modify_all">變更所有一連串的活動。</string>
+  <string name="modify_all_following">變更此項及所有未來的活動。</string>
+  <string name="modify_event">僅變更此活動。</string>
+  <string name="monday_letter">"一"</string>
+  <string name="month_view">"月"</string>
+  <string name="monthly_on_day">"每月 (第 <xliff:g id="day_of_month">%s</xliff:g> 天)"</string>
+  <string name="monthly_on_day_count">"每月 (每 <xliff:g id="ordinal_number">%1$s</xliff:g><xliff:g id="day_of_week">%2$s</xliff:g>)"</string>
+  <string name="more_options_label">"更多選項"</string>
+  <string name="no_calendars">正在等待同步</string>
+  <string name="no_calendars_msg">將會立即顯示事件。</string>
+  <string name="no_title_label">(無主旨)</string>
+  <string name="notes_label">記事</string>
+  <string name="num_events">"活動數目"</string>
+  <string name="ok_label">"確定"</string>
+  <string name="on_label">"開啟"</string>
+  <string name="plus_N_more">"(加上 <xliff:g id="more_count">%d</xliff:g> 其它 \u2026)"</string>
+  <string name="preferences_alerts_ringtone_title">選取響鈴音調</string>
+  <string name="preferences_alerts_sound_title">聲音</string>
+  <string name="preferences_alerts_title">提醒設定</string>
+  <string name="preferences_alerts_type_default">1</string>
+  <string name="preferences_alerts_type_dialog">設定提醒</string>
+  <string name="preferences_alerts_type_title">設定警訊與通知</string>
+  <string name="preferences_alerts_vibrate_title">震動</string>
+  <string name="preferences_default_reminder_default">10</string>
+  <string name="preferences_default_reminder_dialog">設定預設提醒</string>
+  <string name="preferences_default_reminder_title">設定預設提醒</string>
+  <string name="preferences_general_title">行事曆檢視設定</string>
+  <string name="preferences_hide_declined_title">隱藏已經拒絕的活動</string>
+  <string name="preferences_title">"設定"</string>
+  <string name="presence_label">存在</string>
+  <string name="privacy_label">隱私權</string>
+  <string name="reminder">提醒</string>
+  <string name="reminders_label">提醒</string>
+  <string name="reminders_remove_label">移除</string>
+  <string name="remove_calendars">"移除行事曆"</string>
+  <string name="repeat_on_label">"重複週期"</string>
+  <string name="repeats_label">重複</string>
+  <string name="saturday_letter">"六"</string>
+  <string name="save_label">儲存</string>
+  <string name="select_calendars_to_sync">"選取要同步的行事曆"</string>
+  <string name="set_time">"設定時間"</string>
+  <string name="snooze_all_label">"全部延遲"</string>
+  <string name="snooze_label">"延遲"</string>
+  <string name="sunday_letter">"日"</string>
+  <string name="thursday_letter">"四"</string>
+  <string name="to_label">到</string>
+  <string name="tuesday_letter">"二"</string>
+  <string name="until">"直到"</string>
+  <string name="view_event_accept_button">是</string>
+  <string name="view_event_accept_label">出席</string>
+  <string name="view_event_calendar_label">行事曆</string>
+  <string name="view_event_decline_button">否</string>
+  <string name="view_event_decline_label">未出席</string>
+  <string name="view_event_edit">編輯</string>
+  <string name="view_event_no_response_label">(無回應)</string>
+  <string name="view_event_reminders_label">提醒</string>
+  <string name="view_event_response_label">出席?</string>
+  <string name="view_event_tentative_button">可能</string>
+  <string name="view_event_tentative_label">可能出席</string>
+  <string name="view_event_timezone_label">本地時區</string>
+  <string name="view_label">"檢視"</string>
+  <string name="wednesday_letter">"三"</string>
+  <string name="week_view">"週"</string>
+  <string name="weekly">"每週 (每 <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+  <string name="what_label">目標</string>
+  <string name="when_label">時間</string>
+  <string name="where_label">地點</string>
+  <string name="yearly">"每年 (<xliff:g id="dates">%s</xliff:g>)"</string>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644 (file)
index 0000000..89c41e5
--- /dev/null
@@ -0,0 +1,246 @@
+<?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.
+-->
+
+<resources>
+    <!-- Choices for the "Reminder minutes" spinner.
+         These must be kept in sync with the reminder_minutes_values array
+         and the reminder_minutes_labels_abbrev array below.
+    -->
+    <string-array name="reminder_minutes_labels">
+        <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="reminder_minutes_labels_abbrev">
+        <item>5 mins</item>
+        <item>10 mins</item>
+        <item>15 mins</item>
+        <item>20 mins</item>
+        <item>25 mins</item>
+        <item>30 mins</item>
+        <item>45 mins</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="reminder_minutes_values">
+        <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_default_reminder_labels">
+        <item>None</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">
+        <item>"0"</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">
+        <item>"0"</item>
+        <item>"1"</item>
+        <item>"2"</item>
+    </string-array>
+    
+    <!-- Choices for the "Repeats" spinner.
+         These must be kept in sync with the sRepeats[] array
+         of ints in EventActivity.java. -->
+    <string-array name="repeat_strings">
+        <item>"Does not repeat"</item>
+        <item>"Daily"</item>
+        <item>"Weekly"</item>
+        <item>"Monthly"</item>
+        <item>"Yearly"</item>
+    </string-array>
+    <string-array name="every_n_days">
+        <item>"day"</item>
+        <item>"2 days"</item>
+        <item>"3 days"</item>
+        <item>"4 days"</item>
+        <item>"5 days"</item>
+        <item>"6 days"</item>
+        <item>"7 days"</item>
+        <item>"8 days"</item>
+        <item>"9 days"</item>
+        <item>"10 days"</item>
+        <item>"11 days"</item>
+        <item>"12 days"</item>
+        <item>"13 days"</item>
+        <item>"14 days"</item>
+    </string-array>
+    <string-array name="every_n_weeks">
+        <item>"week"</item>
+        <item>"2 weeks"</item>
+        <item>"3 weeks"</item>
+        <item>"4 weeks"</item>
+        <item>"5 weeks"</item>
+        <item>"6 weeks"</item>
+        <item>"7 weeks"</item>
+        <item>"8 weeks"</item>
+        <item>"9 weeks"</item>
+        <item>"10 weeks"</item>
+        <item>"11 weeks"</item>
+        <item>"12 weeks"</item>
+        <item>"13 weeks"</item>
+        <item>"14 weeks"</item>
+    </string-array>
+    <string-array name="every_n_months">
+        <item>"month"</item>
+        <item>"2 months"</item>
+        <item>"3 months"</item>
+        <item>"4 months"</item>
+        <item>"5 months"</item>
+        <item>"6 months"</item>
+        <item>"7 months"</item>
+        <item>"8 months"</item>
+        <item>"9 months"</item>
+        <item>"10 months"</item>
+        <item>"11 months"</item>
+        <item>"12 months"</item>
+        <item>"13 months"</item>
+        <item>"14 months"</item>
+    </string-array>
+    <string-array name="every_n_years">
+        <item>"year"</item>
+        <item>"2 years"</item>
+        <item>"3 years"</item>
+        <item>"4 years"</item>
+        <item>"5 years"</item>
+        <item>"6 years"</item>
+        <item>"7 years"</item>
+        <item>"8 years"</item>
+        <item>"9 years"</item>
+        <item>"10 years"</item>
+        <item>"11 years"</item>
+        <item>"12 years"</item>
+        <item>"13 years"</item>
+        <item>"14 years"</item>
+    </string-array>
+    
+    <string-array name="availability">
+        <item>Show as busy</item>
+        <item>Show as available</item>
+    </string-array>
+    
+    <string-array name="visibility">
+        <item>Default</item>
+        <item>Private</item>
+        <item>Public</item>
+    </string-array>
+    
+    <!-- Order matters, and note that the preference for which day the week starts on is handled
+         elsewhere (and needn't be addressed here). -->
+    <string-array name="day_labels">
+        <item>Sunday</item>
+        <item>Monday</item>
+        <item>Tuesday</item>
+        <item>Wednesday</item>
+        <item>Thursday</item>
+        <item>Friday</item>
+        <item>Saturday</item>
+    </string-array>
+    
+    <string-array name="ordinal_labels">
+        <item>first</item>
+        <item>second</item>
+        <item>third</item>
+        <item>fourth</item>
+        <item>last</item>
+    </string-array>
+
+    <!-- Invitation responses -->    
+    <string-array name="response_labels1">
+        <item>(No response)</item>
+        <item>Yes</item>
+        <item>Maybe</item>
+        <item>No</item>
+    </string-array>
+    <string-array name="response_labels2">
+        <item>Yes</item>
+        <item>Maybe</item>
+        <item>No</item>
+    </string-array>
+
+    <!-- The corresponding indices are defined in DeleteEventHelper.java -->
+    <string-array name="delete_repeating_labels">
+        <item>Only this event</item>
+        <item>This &amp; future events</item>
+        <item>All events</item>
+    </string-array>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644 (file)
index 0000000..b7e3c90
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Calendar/assets/res/any/colors.xml
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <drawable name="selected_day_background">#ffd5d5d5</drawable>
+    <drawable name="event_background">#ffc3d9ff</drawable>
+    <drawable name="pref_background">#ffc3d9ff</drawable>
+    <drawable name="label_foreground">#ff1b38c2</drawable>
+    <drawable name="text_foreground">#ff000000</drawable>
+    <drawable name="base_background">#ffffffff</drawable>
+    <drawable name="panel_label_foreground">#ffe0f0ff</drawable>
+    <drawable name="panel_text_foreground">#ffffffff</drawable>
+    <drawable name="daynames_background">#ff1c1c1c</drawable>
+    <drawable name="title_background">#ff565656</drawable>
+    
+    <color name="black">#ff000000</color>
+    <color name="black_25">#40000000</color>
+    <color name="black_50">#80000000</color>
+    <color name="white">#ffffffff</color>
+    <color name="white_25">#40ffffff</color>
+    <color name="white_50">#80ffffff</color>
+    <color name="grey">#fff0f0f0</color>
+    
+    <color name="calendar_all_day_event_color">#ffffffff</color>
+    <color name="calendar_event_color">#ffffffff</color>
+    <color name="calendar_event_text_color">#ff000000</color>
+
+    <color name="calendar_all_day_background">#ff5a5a5a</color>
+    <color name="other_month_cell">#ffd5d5d5</color>
+    <color name="label_base">#ff1b38c2</color>
+    <color name="week_label">@color/label_base</color>
+    <color name="week_weekday">@color/black</color>
+    <color name="week_weekend">@color/white</color>
+    <color name="other_month">@color/white</color>
+    <color name="calendar_hour_label">@color/white</color>
+    <color name="calendar_ampm_label">#ffbebebe</color>
+    <color name="calendar_hour_background">#ff393939</color>
+    <color name="calendar_hour_selected">#ff808080</color>
+    <color name="calendar_date_banner_background">#ff424242</color>
+    <color name="calendar_date_selected">#ff808080</color>
+    <color name="calendar_date_banner_text_color">@color/white</color>
+    <color name="calendar_grid_area_background">#ff202020</color>
+    <color name="calendar_grid_area_selected">#ff505050</color>
+    <color name="calendar_grid_line_horizontal_color">#ff5a5a5a</color>
+    <color name="calendar_grid_line_vertical_color">#ff5a5a5a</color>
+    <color name="calendar_grid_line_highlight_color">#ff707070</color>
+    <color name="status_background">#fff4d66e</color>
+    <color name="selection">#ffffaa00</color>
+    
+    <color name="month_day_number">#ff404040</color>
+    <color name="month_today_number">#ffffffff</color>
+    <color name="month_other_month">#ff595959</color>
+    <color name="month_other_month_day_number">#ffa0a0a0</color>
+    <color name="month_other_month_name">#ff84868c</color>
+    <color name="month_other_month_banner">#80595959</color>
+    <color name="month_week_banner">#20595959</color>
+    <color name="month_dna_strip_color">#ff63c731</color>
+
+    <color name="conflict_center">#ffff8876</color>
+    <color name="conflict_border">#ffbf6558</color>
+    <color name="event_center">#ff6bd697</color>
+    <color name="event_border">#ff50a071</color>
+</resources>
+
diff --git a/res/values/integers.xml b/res/values/integers.xml
new file mode 100644 (file)
index 0000000..ac44790
--- /dev/null
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources>
+    <integer name="number_of_hours">10</integer>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644 (file)
index 0000000..d4edf8a
--- /dev/null
@@ -0,0 +1,230 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label">Calendar</string>
+
+    <!-- Shared Labels. These labels are shared among the activities. -->
+    <string name="what_label">What</string>
+    <string name="when_label">When</string>
+    <string name="where_label">Where</string>
+    <string name="repeats_label">Repeats</string>
+    <string name="to_label">to</string>
+    <!-- Title of event when no explicit title is defined -->
+    <string name="no_title_label">(No subject)</string>
+    
+    <!-- Reminder format strings -->
+    <plurals name="Nminutes">
+        <item quantity="one">1 minute</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes</item>
+    </plurals>
+    <!-- We use "mins" instead of "minutes" to keep the string short -->
+    <plurals name="Nmins">
+        <item quantity="one">1 min</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> mins</item>
+    </plurals>
+    <plurals name="Nhours">
+        <item quantity="one">1 hour</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item>
+    </plurals>
+    <plurals name="Ndays">
+        <item quantity="one">1 day</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> days</item>
+    </plurals>
+    
+    <!-- Menu items: -->
+    <string name="agenda_view">"Agenda"</string>
+    <string name="day_view">"Day"</string>
+    <string name="week_view">"Week"</string>
+    <string name="month_view">"Month"</string>
+    <string name="event_view">"View event"</string>
+    <string name="event_create">"New event"</string>
+    <string name="event_edit">"Edit event"</string>
+    <string name="event_delete">"Delete event"</string>
+    <string name="goto_today">"Today"</string>
+    <string name="menu_select_calendars">"My calendars"</string>
+    <string name="menu_preferences">"Settings"</string>
+
+    <!-- Month view -->
+    <string name="plus_N_more">"(plus <xliff:g id="more_count">%d</xliff:g> more \u2026)"</string>
+
+    <!-- Select Calendars activity -->
+    <!-- Title of "My calendars" screen -->
+    <string name="calendars_title">"My calendars"</string>
+    <!-- Menu option -->
+    <string name="add_calendars">"Add calendars"</string>
+    <!-- Menu option -->
+    <string name="remove_calendars">"Remove calendars"</string>
+    <string name="select_calendars_to_sync">"Select calendars to sync"</string>
+
+    <!-- Event edit activity -->
+    <!-- Screen title -->
+    <string name="event_edit_title">"Event details"</string>
+    <string name="set_time">"Set the time"</string>
+    <string name="every_label">"Every"</string>
+    <string name="on_label">"On"</string>
+    <string name="sunday_letter">"S"</string>
+    <string name="monday_letter">"M"</string>
+    <string name="tuesday_letter">"T"</string>
+    <string name="wednesday_letter">"W"</string>
+    <string name="thursday_letter">"T"</string>
+    <string name="friday_letter">"F"</string>
+    <string name="saturday_letter">"S"</string>
+    <string name="until">"Until"</string>
+    <string name="repeat_on_label">"Repeat on"</string>
+    <!-- Default value of What field -->
+    <string name="hint_what">"Event name"</string>
+    <!-- Default value of Where field -->
+    <string name="hint_where">"Event location"</string>
+    <!-- Default value of Description field -->
+    <string name="hint_description">"Event description"</string>
+
+    <string name="alert_when_label">When:</string>
+    <string name="alert_where_label">Where:</string>
+    <!-- Notification window messages: -->
+    <string name="alert_missed_events_single">(<xliff:g id="reminder_count">%s</xliff:g> more reminder)</string>
+    <string name="alert_missed_events_multiple">(<xliff:g id="reminder_count">%s</xliff:g> more reminders)</string>
+    
+    <!-- Event info/edit screen labels:-->
+    <string name="event_info_title">View event</string>
+    <string name="event_info_title_invite">Meeting invitation</string>
+    <string name="reminder">Reminder</string>
+    <string name="all_day_event">All-day event</string>
+    <string name="notes_label">Notes</string>
+    <string name="from_label">From</string>
+    <!-- Menu item -->
+    <string name="add_new_reminder">Add reminder</string>
+        
+    <!-- EditEventActivity specific strings: -->
+    <!-- Heading on Event details screen -->
+    <string name="edit_event_to_label">To</string>
+    <!-- Heading on Event details screen -->
+    <string name="edit_event_from_label">From</string>
+    <!-- Check box label -->
+    <string name="edit_event_all_day_label">All day</string>
+    <!-- Heading on Event details screen -->
+    <string name="edit_event_calendar_label">Calendar</string>
+    <!-- Menu item -->
+    <string name="edit_event_show_extra_options">Show extra options</string>
+    <!-- Menu item -->
+    <string name="edit_event_hide_extra_options">Hide extra options</string>
+    <!-- Heading on Event details screen -->
+    <string name="description_label">Description</string>
+    <!-- Heading on Event details screen -->
+    <string name="presence_label">Presence</string>
+    <!-- Heading on Event details screen -->
+    <string name="privacy_label">Privacy</string>
+    <!-- Heading on Event details screen -->
+    <string name="reminders_label">Reminders</string>
+    <string name="reminders_remove_label">Remove</string>
+    
+    <!-- View Event -->
+    <string name="view_event_edit">Edit</string>
+    <string name="view_event_calendar_label">Calendar</string>
+    <string name="view_event_timezone_label">Local time zone</string>
+    <string name="view_event_response_label">Attending?</string>
+    <!-- Heading on View event screen -->
+    <string name="view_event_reminders_label">Reminders</string>
+    <string name="view_event_accept_label">Attending</string>
+    <string name="view_event_tentative_label">Maybe attending</string>
+    <string name="view_event_decline_label">Not attending</string>
+    <string name="view_event_no_response_label">(No response)</string>
+    <string name="view_event_accept_button">Yes</string>
+    <string name="view_event_tentative_button">Maybe</string>
+    <string name="view_event_decline_button">No</string>
+    
+    <!-- Agenda View strings -->
+    <string name="agenda_when_label">When</string>
+    <string name="agenda_where_label">Where</string>
+    <string name="agenda_today">Today</string>
+    
+    <!-- ICS Import activity -->
+    <string name="num_events">"Num events"</string>
+
+    <!-- Button labels: -->
+    <string name="done_label">"Done"</string>
+    <string name="edit_event_label">"Edit event"</string>
+    <string name="ok_label">"OK"</string>
+    <string name="delete_label">"Delete"</string>
+    <string name="delete_event_label">"Delete event"</string>
+    <string name="save_label">Save</string>
+    <string name="discard_label">Discard changes</string>
+    <string name="import_label">"Import"</string>
+    <string name="cancel_label">"Cancel"</string>
+    <string name="more_options_label">"More options"</string>
+    <string name="view_label">"View"</string>
+    <string name="snooze_label">"Snooze"</string>
+    <string name="dismiss_label">"Dismiss"</string>
+    <!-- Button labels on expanded notification reminders: -->
+    <string name="snooze_all_label">"Snooze all"</string>
+    <string name="dismiss_all_label">"Dismiss all"</string>
+
+    <string name="calendar_item_calendar_text"></string>
+    <!-- Repetition dialog options: -->
+    <string name="does_not_repeat">Does not repeat</string>
+    <string name="daily">Daily</string>
+    <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+    <string name="weekly">"Weekly (every <xliff:g id="days_of_week">%s</xliff:g>)"</string>
+
+    <!-- Example: "Monthly (every first Sunday)" -->
+    <!--   1: "first" -->
+    <!--   2: "Sunday" -->
+    <string name="monthly_on_day_count">"Monthly (every <xliff:g id="ordinal_number">%1$s</xliff:g> <xliff:g id="day_of_week">%2$s</xliff:g>)"</string> 
+    
+    <string name="monthly_on_day">"Monthly (on day <xliff:g id="day_of_month">%s</xliff:g>)"</string>
+    <string name="yearly">"Yearly (on <xliff:g id="dates">%s</xliff:g>)"</string>
+    <string name="custom">"Custom\u2026 (cannot customize on phone)"</string>
+    
+    <string name="modify_event">Change only this event.</string>
+    <string name="modify_all">Change all events in the series.</string>
+    <string name="modify_all_following">Change this and all future events.</string>
+    
+    <!-- Dialogs -->
+    <!-- Confirmation dialog message -->
+    <string name="delete_this_event_title">This event will be deleted.</string>
+    <!-- Confirmation dialog title -->
+    <string name="delete_title">Delete</string>
+
+    <string name="preferences_title">"Settings"</string>
+    <!-- Title for settings section -->
+    <string name="preferences_general_title">Calendar view setting</string>
+    <!-- Title for settings section -->
+    <string name="preferences_alerts_title">Reminder settings</string>
+    <!-- Settings check box label -->
+    <string name="preferences_hide_declined_title">Hide declined events</string>
+    <!-- Settings option -->
+    <string name="preferences_alerts_type_title">Set alerts &amp; notifications</string>
+    <!--  -->
+    <string name="preferences_alerts_type_dialog">Set reminder</string>
+    <string name="preferences_alerts_type_default">1</string>
+    <!-- Settings check box label -->
+    <string name="preferences_alerts_vibrate_title">Vibrate</string>
+    <!-- Settings option -->
+    <string name="preferences_alerts_sound_title">Sound</string>
+    <!-- Title of ringtone selector dialog -->
+    <string name="preferences_alerts_ringtone_title">Select ringtone</string>
+    <!-- Settings option -->
+    <string name="preferences_default_reminder_title">Set default reminder</string>
+    <!-- Title of default reminder dialog -->
+    <string name="preferences_default_reminder_dialog">Set default reminder</string>
+    <!-- Value of default reminder time -->
+    <string name="preferences_default_reminder_default">10</string>
+    <!-- Screen title if user goes into Calendar before events have synced -->
+    <string name="no_calendars">Waiting for sync</string>
+    <!-- Appears on screen if user goes into Calendar before events have synced -->
+    <string name="no_calendars_msg">Your events will appear shortly.</string>
+</resources>
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644 (file)
index 0000000..5a41f41
--- /dev/null
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Calendar/assets/res/any/styles.xml
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <style name="Activity" parent="android:Theme">
+        <item name="android:windowBackground">@null</item>
+    </style>
+
+    <style name="Activity.Day">
+        <item name="android:windowBackground">@null</item>
+    </style>
+
+
+    <style name="Activity.Week">
+        <item name="android:windowBackground">@null</item>
+    </style>
+
+    <style name="Alert" parent="android:Theme.Dialog">
+        <item name="android:windowBackground">@null</item>
+    </style>
+
+    <style name="MonthView_DayLabel">
+        <item name="android:layout_width">29dip</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">2dip</item>
+        <item name="android:textAppearance">@style/TextAppearance.MonthView_DayLabel</item>
+    </style>
+    
+    <style name="TextAppearance" parent="android:TextAppearance">
+    </style>
+
+    <style name="TextAppearance.MonthView_DayLabel">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">#ffffffff</item>
+    </style>
+    
+    <style name="TextAppearance.AgendaView_TitleLabel">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@color/black</item>
+    </style>
+    
+    <style name="TextAppearance.AgendaView_ValueLabel">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/black</item>
+    </style>
+    
+    <style name="TextAppearance.EditEvent_Label">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+    
+    <style name="TextAppearance.EditEvent_Value">
+        <item name="android:textSize">14sp</item>
+    </style>
+    
+    <style name="TextAppearance.Alert_Title">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">@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">@color/white</item>
+    </style>
+    
+    <style name="TextAppearance.Alert_Value">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <style name="TextAppearance.title">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">#ffffffff</item>
+    </style>
+
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
new file mode 100644 (file)
index 0000000..061170e
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory android:title="@string/preferences_general_title">
+        <CheckBoxPreference
+            android:key="preferences_hide_declined"
+            android:defaultValue="false"
+            android:title="@string/preferences_hide_declined_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" />
+        
+        <RingtonePreference
+            android:layout="?android:attr/preferenceLayoutChild"
+            android:key="preferences_alerts_ringtone"
+            android:title="@string/preferences_alerts_ringtone_title"
+            android:ringtoneType="notification"
+            android:defaultValue="content://settings/system/notification_sound" />
+        
+        <CheckBoxPreference
+            android:layout="?android:attr/preferenceLayoutChild"
+            android:key="preferences_alerts_vibrate"
+            android:defaultValue="false"
+            android:title="@string/preferences_alerts_vibrate_title" />
+        
+        <ListPreference
+            android:key="preferences_default_reminder"
+            android:defaultValue="@string/preferences_default_reminder_default"
+            android:title="@string/preferences_default_reminder_title"
+            android:entries="@array/preferences_default_reminder_labels"
+            android:entryValues="@array/preferences_default_reminder_values"
+            android:dialogTitle="@string/preferences_default_reminder_dialog" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/src/com/android/calendar/AgendaActivity.java b/src/com/android/calendar/AgendaActivity.java
new file mode 100644 (file)
index 0000000..40744aa
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * 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.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+import android.provider.Calendar;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Instances;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.ViewSwitcher;
+import dalvik.system.VMRuntime;
+
+public class AgendaActivity extends Activity implements ViewSwitcher.ViewFactory, Navigator {
+
+    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+
+    private static final String[] PROJECTION = new String[] {
+        Instances._ID,              // 0
+        Instances.TITLE,            // 1
+        Instances.EVENT_LOCATION,   // 2
+        Instances.ALL_DAY,          // 3
+        Instances.HAS_ALARM,        // 4
+        Instances.COLOR,            // 5
+        Instances.RRULE,            // 6
+        Instances.BEGIN,            // 7
+        Instances.END,              // 8
+        Instances.EVENT_ID,         // 9
+        Instances.START_DAY,        // 10  Julian start day
+        Instances.END_DAY,          // 11  Julian end day
+    };
+
+    public static final int INDEX_TITLE = 1;
+    public static final int INDEX_EVENT_LOCATION = 2;
+    public static final int INDEX_ALL_DAY = 3;
+    public static final int INDEX_HAS_ALARM = 4;
+    public static final int INDEX_COLOR = 5;
+    public static final int INDEX_RRULE = 6;
+    public static final int INDEX_BEGIN = 7;
+    public static final int INDEX_END = 8;
+    public static final int INDEX_EVENT_ID = 9;
+    public static final int INDEX_START_DAY = 10;
+    public static final int INDEX_END_DAY = 11;
+
+    public static final String AGENDA_SORT_ORDER = "startDay ASC, begin ASC, title ASC";
+
+    private static final long INITIAL_HEAP_SIZE = 4*1024*1024;
+
+    private ContentResolver mContentResolver;
+
+    private ViewSwitcher mViewSwitcher;
+
+    private QueryHandler mQueryHandler;
+    private DeleteEventHelper mDeleteEventHelper;
+    private Time mTime;
+
+    /**
+     * This records the start time parameter for the last query sent to the
+     * AsyncQueryHandler so that we don't send it duplicate query requests.
+     */
+    private Time mLastQueryTime = new Time();
+
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+
+            // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
+            if (!isFinishing()) {
+                AgendaListView next = (AgendaListView) mViewSwitcher.getNextView();
+                next.setCursor(cursor);
+                mViewSwitcher.showNext();
+                selectTime();
+            } else {
+                cursor.close();
+            }
+        }
+    }
+
+    private class AgendaListView extends ListView {
+        private Cursor mCursor;
+        private AgendaByDayAdapter mDayAdapter;
+        private AgendaAdapter mAdapter;
+
+        public AgendaListView(Context context) {
+            super(context, null);
+            setDivider(null);
+            setSelector(android.R.color.transparent);
+            setOnItemClickListener(mOnItemClickListener);
+            setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            mAdapter = new AgendaAdapter(AgendaActivity.this, R.layout.agenda_item);
+            mDayAdapter = new AgendaByDayAdapter(AgendaActivity.this, mAdapter);
+        }
+
+        public void setCursor(Cursor cursor) {
+            if (mCursor != null) {
+                mCursor.close();
+            }
+            mCursor = cursor;
+            mDayAdapter.calculateDays(cursor);
+            mAdapter.changeCursor(cursor);
+            setAdapter(mDayAdapter);
+        }
+
+        public Cursor getCursor() {
+            return mCursor;
+        }
+
+        public AgendaByDayAdapter getDayAdapter() {
+            return mDayAdapter;
+        }
+
+        @Override protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            if (mCursor != null) {
+                mCursor.close();
+            }
+        }
+
+        private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
+            public void onItemClick(AdapterView a, View v, int position, long id) {
+                if (id != -1) {
+                    // Switch to the EventInfo view
+                    mCursor.moveToPosition(mDayAdapter.getCursorPosition(position));
+                    long eventId = mCursor.getLong(INDEX_EVENT_ID);
+                    Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+                    intent.putExtra(Calendar.EVENT_BEGIN_TIME, mCursor.getLong(INDEX_BEGIN));
+                    intent.putExtra(Calendar.EVENT_END_TIME, mCursor.getLong(INDEX_END));
+                    startActivity(intent);
+                }
+            }
+        };
+    }
+
+    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)) {
+                clearLastQueryTime();
+                renewCursor();
+            }
+        }
+    };
+
+    private ContentObserver mObserver = new ContentObserver(new Handler()) {
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            clearLastQueryTime();
+            renewCursor();
+        }
+    };
+
+    @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
+        long oldHeapSize = VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
+
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.agenda_activity);
+
+        mContentResolver = getContentResolver();
+        mQueryHandler = new QueryHandler(mContentResolver);
+
+        // Preserve the same month and event selection if this activity is
+        // being restored due to an orientation change
+        mTime = new Time();
+        if (icicle != null) {
+            mTime.set(icicle.getLong(BUNDLE_KEY_RESTORE_TIME));
+        } else {
+            mTime.set(Utils.timeFromIntent(getIntent()));
+        }
+        setTitle(Utils.formatMonthYear(mTime));
+
+        mViewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
+        mViewSwitcher.setFactory(this);
+
+        // Record Agenda View as the (new) default detailed view.
+        String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.AGENDA_VIEW_ID];
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString);
+
+        // Record Agenda View as the (new) start view
+        editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
+        editor.commit();
+
+        mDeleteEventHelper = new DeleteEventHelper(this, false /* don't exit when done */);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        clearLastQueryTime();
+        renewCursor();
+
+        // 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
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putLong(BUNDLE_KEY_RESTORE_TIME, getSelectedTime());
+    }
+
+    @Override
+    protected void onPause() {
+        super.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)
+                AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
+                Cursor cursor = current.getCursor();
+                if (cursor != null) {
+                    int position = current.getSelectedItemPosition();
+                    position = current.getDayAdapter().getCursorPosition(position);
+                    if (position >= 0) {
+                        cursor.moveToPosition(position);
+                        long begin = cursor.getLong(INDEX_BEGIN);
+                        long end = cursor.getLong(INDEX_END);
+                        long eventId = cursor.getLong(INDEX_EVENT_ID);
+                        mDeleteEventHelper.delete(begin, end, eventId, -1);
+                    }
+                }
+            }
+                break;
+
+            case KeyEvent.KEYCODE_BACK:
+                finish();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * Clears the cached value for the last query time so that renewCursor()
+     * will force a requery of the Calendar events.
+     */
+    private void clearLastQueryTime() {
+        mLastQueryTime.year = 0;
+        mLastQueryTime.month = 0;
+    }
+
+    private void renewCursor() {
+        // Avoid batching up repeated queries for the same month.  This can
+        // happen if the user scrolls with the trackball too fast.
+        if (mLastQueryTime.month == mTime.month && mLastQueryTime.year == mTime.year) {
+            return;
+        }
+
+        // Query all instances for the current month
+        Time time = new Time();
+        time.year = mTime.year;
+        time.month = mTime.month;
+        long start = time.normalize(true);
+
+        time.month++;
+        long end = time.normalize(true);
+
+        StringBuilder path = new StringBuilder();
+        path.append(start);
+        path.append('/');
+        path.append(end);
+
+        // Respect the preference to show/hide declined events
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED,
+                false);
+
+        Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, path.toString());
+
+        String selection;
+        if (hideDeclined) {
+            selection = Calendars.SELECTED + "=1 AND " +
+                    Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
+        } else {
+            selection = Calendars.SELECTED + "=1";
+        }
+
+        // Cancel any previous queries that haven't started yet.  This
+        // isn't likely to happen since we already avoid sending
+        // a duplicate query for the same month as the previous query.
+        // But if the user quickly wiggles the trackball back and forth,
+        // he could generate a stream of queries.
+        mQueryHandler.cancelOperation(0);
+
+        mLastQueryTime.month = mTime.month;
+        mLastQueryTime.year = mTime.year;
+        mQueryHandler.startQuery(0, null, uri, PROJECTION, selection, null,
+                AGENDA_SORT_ORDER);
+    }
+
+    private void selectTime() {
+        // Selects the first event of the day
+        AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
+        if (current.getCursor() == null) {
+            return;
+        }
+
+        int position = current.getDayAdapter().findDayPositionNearestTime(mTime);
+        current.setSelection(position);
+    }
+
+    /* ViewSwitcher.ViewFactory interface methods */
+    public View makeView() {
+        AgendaListView agendaListView = new AgendaListView(this);
+        return agendaListView;
+    }
+
+    /* Navigator interface methods */
+    public void goToToday() {
+        Time now = new Time();
+        now.set(System.currentTimeMillis());
+        goTo(now);
+    }
+
+    public void goTo(Time time) {
+        if (mTime.year == time.year && mTime.month == time.month) {
+            mTime = time;
+            selectTime();
+        } else {
+            mTime = time;
+        }
+    }
+
+    public long getSelectedTime() {
+        // Update the current time based on the selected event
+        AgendaListView current = (AgendaListView) mViewSwitcher.getCurrentView();
+        int position = current.getSelectedItemPosition();
+        position = current.getDayAdapter().getCursorPosition(position);
+        Cursor cursor = current.getCursor();
+        if (position >= 0 && position < cursor.getCount()) {
+            cursor.moveToPosition(position);
+            mTime.set(cursor.getLong(INDEX_BEGIN));
+        }
+
+        return mTime.toMillis(true);
+    }
+
+    public boolean getAllDay() {
+        return false;
+    }
+}
+
diff --git a/src/com/android/calendar/AgendaAdapter.java b/src/com/android/calendar/AgendaAdapter.java
new file mode 100644 (file)
index 0000000..3ec576b
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.provider.Calendar.Reminders;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class AgendaAdapter extends ResourceCursorAdapter {
+    
+    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 Resources mResources;
+    private static ArrayList<Integer> sReminderValues;
+    private static String[] sReminderLabels;
+
+    public AgendaAdapter(Context context, int resource) {
+        super(context, resource, null);
+        mResources = context.getResources();
+    }
+    
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        ImageView stripe = (ImageView) view.findViewById(R.id.vertical_stripe);
+        int color = cursor.getInt(AgendaActivity.INDEX_COLOR) & 0xbbffffff;
+        stripe.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        
+        // What
+        TextView title = (TextView) view.findViewById(R.id.title);
+        String titleString = cursor.getString(AgendaActivity.INDEX_TITLE);
+        if (titleString == null || titleString.length() == 0) {
+            titleString = mResources.getString(R.string.no_title_label);
+        }
+        title.setText(titleString);
+        
+        // When
+        TextView when = (TextView) view.findViewById(R.id.when);
+        long begin = cursor.getLong(AgendaActivity.INDEX_BEGIN);
+        long end = cursor.getLong(AgendaActivity.INDEX_END);
+        boolean allDay = cursor.getInt(AgendaActivity.INDEX_ALL_DAY) != 0;
+        int flags;
+        String whenString;
+        if (allDay) {
+            flags = DateUtils.FORMAT_UTC;
+        } else {
+            flags = DateUtils.FORMAT_SHOW_TIME;
+        }
+        if (DateFormat.is24HourFormat(context)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        whenString = DateUtils.formatDateRange(begin, end, flags);
+        when.setText(whenString);
+        
+        // Repeating info
+        View repeatContainer = view.findViewById(R.id.repeat_icon);
+        String rrule = cursor.getString(AgendaActivity.INDEX_RRULE);
+        if (rrule != null) {
+            repeatContainer.setVisibility(View.VISIBLE);
+        } else {
+            repeatContainer.setVisibility(View.GONE);
+        }
+        
+        // Reminder
+        boolean hasAlarm = cursor.getInt(AgendaActivity.INDEX_HAS_ALARM) != 0;
+        if (hasAlarm) {
+            updateReminder(view, context, begin, cursor.getLong(AgendaActivity.INDEX_EVENT_ID));
+        }
+        
+        // Where
+        TextView where = (TextView) view.findViewById(R.id.where);
+        String whereString = cursor.getString(AgendaActivity.INDEX_EVENT_LOCATION);
+        if (whereString != null && whereString.length() > 0) {
+            where.setVisibility(View.VISIBLE);
+            where.setText(whereString);
+        } else {
+            where.setVisibility(View.GONE);
+        }
+    }
+
+    public static void updateReminder(View view, Context context, long begin, long eventId) {
+        ContentResolver cr = context.getContentResolver();
+        Uri uri = Reminders.CONTENT_URI;
+        String where = String.format(REMINDERS_WHERE, eventId);
+        
+        Cursor remindersCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
+        if (remindersCursor != null) {
+            LayoutInflater inflater =
+                    (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            LinearLayout parent = (LinearLayout) view.findViewById(R.id.reminders_container);
+            parent.removeAllViews();
+            while (remindersCursor.moveToNext()) {
+                int alarm = remindersCursor.getInt(REMINDERS_INDEX_MINUTES);
+                String before = EditEvent.constructReminderLabel(context, alarm, true);
+                LinearLayout reminderItem = (LinearLayout)
+                        inflater.inflate(R.layout.agenda_reminder_item, null);
+                TextView reminderItemText = (TextView) reminderItem.findViewById(R.id.reminder);
+                reminderItemText.setText(before);
+                parent.addView(reminderItem);
+            }
+        }
+        remindersCursor.close();
+    }
+}
+
diff --git a/src/com/android/calendar/AgendaByDayAdapter.java b/src/com/android/calendar/AgendaByDayAdapter.java
new file mode 100644 (file)
index 0000000..a695693
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ * 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 android.content.Context;
+import android.database.Cursor;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class AgendaByDayAdapter extends BaseAdapter {
+    private static final int TYPE_DAY = 0;
+    private static final int TYPE_MEETING = 1;
+    private static final int TYPE_LAST = 2;
+
+    private final Context mContext;
+    private final AgendaAdapter mAgendaAdapter;
+    private final LayoutInflater mInflater;
+    private ArrayList<RowInfo> mRowInfo;
+    private int mTodayJulianDay;
+    private Time mTime = new Time();
+
+    public AgendaByDayAdapter(Context context, AgendaAdapter agendaAdapter) {
+        mContext = context;
+        mAgendaAdapter = agendaAdapter;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    public int getCount() {
+        if (mRowInfo != null) {
+            return mRowInfo.size();
+        }
+        return mAgendaAdapter.getCount();
+    }
+
+    public Object getItem(int position) {
+        if (mRowInfo != null) {
+            RowInfo row = mRowInfo.get(position);
+            if (row.mType == TYPE_DAY) {
+                return row;
+            } else {
+                return mAgendaAdapter.getItem(row.mData);
+            }
+        }
+        return mAgendaAdapter.getItem(position);
+    }
+
+    public long getItemId(int position) {
+        if (mRowInfo != null) {
+            RowInfo row = mRowInfo.get(position);
+            if (row.mType == TYPE_DAY) {
+                return position;
+            } else {
+                return mAgendaAdapter.getItemId(row.mData);
+            }
+        }
+        return mAgendaAdapter.getItemId(position);
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return TYPE_LAST;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mRowInfo != null && mRowInfo.size() > position ?
+                mRowInfo.get(position).mType : TYPE_DAY;
+    }
+
+    private static class ViewHolder {
+        TextView dateView;
+        TextView dayOfWeekView;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if ((mRowInfo == null) || (position > mRowInfo.size())) {
+            // If we have no row info, mAgendaAdapter returns the view.
+            return mAgendaAdapter.getView(position, convertView, parent);
+        }
+
+        RowInfo row = mRowInfo.get(position);
+        if (row.mType == TYPE_DAY) {
+            ViewHolder holder;
+            View agendaDayView;
+            if ((convertView == null) || (convertView.getTag() == null)) {
+                // Create a new AgendaView with a ViewHolder for fast access to
+                // views w/o calling findViewById()
+                holder = new ViewHolder();
+                agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false);
+                holder.dateView = (TextView) agendaDayView.findViewById(R.id.date);
+                holder.dayOfWeekView = (TextView) agendaDayView.findViewById(R.id.day_of_week);
+                agendaDayView.setTag(holder);
+            } else {
+                agendaDayView = convertView;
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            // Re-use the member variable "mTime" which is set to the local timezone.
+            Time date = mTime;
+            long millis = date.setJulianDay(row.mData);
+            int flags = DateUtils.FORMAT_NUMERIC_DATE;
+            holder.dateView.setText(DateUtils.formatDateRange(millis, millis, flags));
+
+            if (row.mData == mTodayJulianDay) {
+                holder.dayOfWeekView.setText(R.string.agenda_today);
+            } else {
+                int weekDay = date.weekDay + Calendar.SUNDAY;
+                holder.dayOfWeekView.setText(DateUtils.getDayOfWeekString(weekDay,
+                        DateUtils.LENGTH_LONG));
+            }
+            return agendaDayView;
+        } else if (row.mType == TYPE_MEETING) {
+            return mAgendaAdapter.getView(row.mData, convertView, parent);
+        } else {
+            // Error
+            throw new IllegalStateException("Unknown event type:" + row.mType);
+        }
+    }
+
+    public void clearDayHeaderInfo() {
+        mRowInfo = null;
+    }
+
+    public void calculateDays(Cursor cursor) {
+        ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
+        int prevStartDay = -1;
+        Time time = new Time();
+        long now = System.currentTimeMillis();
+        time.set(now);
+        mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
+        LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>();
+        for (int position = 0; cursor.moveToNext(); position++) {
+            boolean allDay = cursor.getInt(AgendaActivity.INDEX_ALL_DAY) != 0;
+            int startDay = cursor.getInt(AgendaActivity.INDEX_START_DAY);
+
+            if (startDay != prevStartDay) {
+                // Check if we skipped over any empty days
+                if (prevStartDay == -1) {
+                    rowInfo.add(new RowInfo(TYPE_DAY, startDay));
+                } else {
+                    // If there are any multiple-day events that span the empty
+                    // range of days, then create day headers and events for
+                    // those multiple-day events.
+                    boolean dayHeaderAdded = false;
+                    for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) {
+                        dayHeaderAdded = false;
+                        Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
+                        while (iter.hasNext()) {
+                            MultipleDayInfo info = iter.next();
+                            // If this event has ended then remove it from the
+                            // list.
+                            if (info.mEndDay < currentDay) {
+                                iter.remove();
+                                continue;
+                            }
+
+                            // If this is the first event for the day, then
+                            // insert a day header.
+                            if (!dayHeaderAdded) {
+                                rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
+                                dayHeaderAdded = true;
+                            }
+                            rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
+                        }
+                    }
+
+                    // If the day header was not added for the start day, then
+                    // add it now.
+                    if (!dayHeaderAdded) {
+                        rowInfo.add(new RowInfo(TYPE_DAY, startDay));
+                    }
+                }
+                prevStartDay = startDay;
+            }
+
+            // Add in the event for this cursor position
+            rowInfo.add(new RowInfo(TYPE_MEETING, position));
+
+            // If this event spans multiple days, then add it to the multipleDay
+            // list.
+            int endDay = cursor.getInt(AgendaActivity.INDEX_END_DAY);
+            if (endDay > startDay) {
+                multipleDayList.add(new MultipleDayInfo(position, endDay));
+            }
+        }
+
+        // There are no more cursor events but we might still have multiple-day
+        // events left.  So create day headers and events for those.
+        if (prevStartDay > 0) {
+            // Get the Julian day for the last day of this month.  To do that,
+            // we set the date to one less than the first day of the next month,
+            // and then normalize.
+            time.setJulianDay(prevStartDay);
+            time.month += 1;
+            time.monthDay = 0;  // monthDay starts with 1, so this is the previous day
+            long millis = time.normalize(true /* ignore isDst */);
+            int lastDayOfMonth = Time.getJulianDay(millis, time.gmtoff);
+
+            for (int currentDay = prevStartDay + 1; currentDay <= lastDayOfMonth; currentDay++) {
+                boolean dayHeaderAdded = false;
+                Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
+                while (iter.hasNext()) {
+                    MultipleDayInfo info = iter.next();
+                    // If this event has ended then remove it from the
+                    // list.
+                    if (info.mEndDay < currentDay) {
+                        iter.remove();
+                        continue;
+                    }
+
+                    // If this is the first event for the day, then
+                    // insert a day header.
+                    if (!dayHeaderAdded) {
+                        rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
+                        dayHeaderAdded = true;
+                    }
+                    rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
+                }
+            }
+        }
+        mRowInfo = rowInfo;
+    }
+
+    private static class RowInfo {
+        // 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 Julian day.  Otherwise
+        // mType is TYPE_MEETING and mData is the cursor position.
+        final int mData;
+
+        RowInfo(int type, int data) {
+            mType = type;
+            mData = data;
+        }
+    }
+
+    private static class MultipleDayInfo {
+        final int mPosition;
+        final int mEndDay;
+
+        MultipleDayInfo(int position, int endDay) {
+            mPosition = position;
+            mEndDay = endDay;
+        }
+    }
+
+    /**
+     * Searches for the day that matches the given Time object and returns the
+     * list position of that day.  If there are no events for that day, then it
+     * finds the nearest day (before or after) that has events and returns the
+     * list position for that day.
+     *
+     * @param time the date to search for
+     * @return the cursor position of the first event for that date, or zero
+     * if no match was found
+     */
+    public int findDayPositionNearestTime(Time time) {
+        if (mRowInfo == null) {
+            return 0;
+        }
+        long millis = time.toMillis(false /* use isDst */);
+        int julianDay = Time.getJulianDay(millis, time.gmtoff);
+        int minDistance = 1000;  // some big number
+        int minIndex = 0;
+        int len = mRowInfo.size();
+        for (int index = 0; index < len; index++) {
+            RowInfo row = mRowInfo.get(index);
+            if (row.mType == TYPE_DAY) {
+                int distance = Math.abs(julianDay - row.mData);
+                if (distance == 0) {
+                    return index;
+                }
+                if (distance < minDistance) {
+                    minDistance = distance;
+                    minIndex = index;
+                }
+            }
+        }
+
+        // We didn't find an exact match so take the nearest day that had
+        // events.
+        return minIndex;
+    }
+
+    /**
+     * Finds the Julian day containing the event at the given position.
+     *
+     * @param position the list position of an event
+     * @return the Julian day containing that event
+     */
+    public int findJulianDayFromPosition(int position) {
+        if (mRowInfo == null || position < 0) {
+            return 0;
+        }
+
+        int len = mRowInfo.size();
+        if (position >= len) return 0;  // no row info at this position
+
+        for (int index = position; index >= 0; index--) {
+            RowInfo row = mRowInfo.get(index);
+            if (row.mType == TYPE_DAY) {
+                return row.mData;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Converts a list position to a cursor position.  The list contains
+     * day headers as well as events.  The cursor contains only events.
+     *
+     * @param listPos the list position of an event
+     * @return the corresponding cursor position of that event
+     */
+    public int getCursorPosition(int listPos) {
+        if (mRowInfo != null && listPos >= 0) {
+            RowInfo row = mRowInfo.get(listPos);
+            if (row.mType == TYPE_MEETING) {
+                return row.mData;
+            }
+        }
+        return listPos;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        if (mRowInfo != null && position < mRowInfo.size()) {
+            RowInfo row = mRowInfo.get(position);
+            return row.mType == TYPE_MEETING;
+        }
+        return true;
+    }
+}
+
diff --git a/src/com/android/calendar/AlertActivity.java b/src/com/android/calendar/AlertActivity.java
new file mode 100644 (file)
index 0000000..52b1a7d
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * 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.app.Activity;
+import android.app.AlarmManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.PixelFormat;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Calendar;
+import android.provider.Calendar.CalendarAlerts;
+import android.provider.Calendar.Events;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * The alert panel that pops up when there is a calendar event alarm.
+ * This activity is started by an intent that specifies an event id.
+  */
+public class AlertActivity extends Activity {
+    
+    // The default snooze delay: 5 minutes
+    public static final long SNOOZE_DELAY = 5 * 60 * 1000L;
+    
+    private static final String[] PROJECTION = new String[] { 
+        CalendarAlerts._ID,              // 0
+        CalendarAlerts.TITLE,            // 1
+        CalendarAlerts.EVENT_LOCATION,   // 2
+        CalendarAlerts.ALL_DAY,          // 3
+        CalendarAlerts.BEGIN,            // 4
+        CalendarAlerts.END,              // 5
+        CalendarAlerts.EVENT_ID,         // 6
+        CalendarAlerts.COLOR,            // 7
+        CalendarAlerts.RRULE,            // 8
+        CalendarAlerts.HAS_ALARM,        // 9
+        CalendarAlerts.STATE,            // 10
+        CalendarAlerts.ALARM_TIME,       // 11
+    };
+    
+    public static final int INDEX_TITLE = 1;
+    public static final int INDEX_EVENT_LOCATION = 2;
+    public static final int INDEX_ALL_DAY = 3;
+    public static final int INDEX_BEGIN = 4;
+    public static final int INDEX_END = 5;
+    public static final int INDEX_EVENT_ID = 6;
+    public static final int INDEX_COLOR = 7;
+    public static final int INDEX_RRULE = 8;
+    public static final int INDEX_HAS_ALARM = 9;
+    public static final int INDEX_STATE = 10;
+    public static final int INDEX_ALARM_TIME = 11;
+    
+    // We use one notification id for all events so that we don't clutter
+    // the notification screen.  It doesn't matter what the id is, as long
+    // as it is used consistently everywhere.
+    public static final int NOTIFICATION_ID = 0;
+    
+    private ContentResolver mResolver;
+    private AlertAdapter mAdapter;
+    private QueryHandler mQueryHandler;
+    private Cursor mCursor;
+    private ListView mListView;
+    private Button mSnoozeAllButton;
+    private Button mDismissAllButton;
+    
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
+            if (!isFinishing()) {
+                mCursor = cursor;
+                mAdapter.changeCursor(cursor);
+                
+                // The results are in, enable the buttons
+                mSnoozeAllButton.setEnabled(true);
+                mDismissAllButton.setEnabled(true);
+            } else {
+                cursor.close();
+            }
+        }
+        
+    }
+    
+    private OnItemClickListener mViewListener = new OnItemClickListener() {
+
+        public void onItemClick(AdapterView parent, View view, int position,
+                long i) {
+            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
+            cursor.updateInt(INDEX_STATE, CalendarAlerts.DISMISSED);
+            cursor.commitUpdates();
+            
+            startActivity(intent);
+            alertActivity.finish();
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.alert_activity);
+        
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.FILL_PARENT,             // width
+                ViewGroup.LayoutParams.FILL_PARENT,             // height
+                WindowManager.LayoutParams.TYPE_TOAST,          // type
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND,    // flags
+                PixelFormat.TRANSLUCENT);                       // format
+        
+        // Get the dim amount from the theme
+        TypedArray a = obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+        lp.dimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
+        a.recycle();
+
+        getWindow().setAttributes(lp);
+        
+        mResolver = getContentResolver();
+        mQueryHandler = new QueryHandler(mResolver);
+        mAdapter = new AlertAdapter(this, R.layout.alert_item);
+        
+        mListView = (ListView) findViewById(R.id.alert_container);
+        mListView.setItemsCanFocus(true);
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(mViewListener);
+        
+        mSnoozeAllButton = (Button) findViewById(R.id.snooze_all);
+        mSnoozeAllButton.setOnClickListener(mSnoozeAllListener);
+        mDismissAllButton = (Button) findViewById(R.id.dismiss_all);
+        mDismissAllButton.setOnClickListener(mDismissAllListener);
+
+        // Disable the buttons, since they need mCursor, which is created asynchronously
+        mSnoozeAllButton.setEnabled(false);
+        mDismissAllButton.setEnabled(false);
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        // If the cursor is null, start the async handler. If it is not null just requery.
+        if (mCursor == null) {
+            Uri uri = CalendarAlerts.CONTENT_URI_BY_INSTANCE;
+            String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
+            mQueryHandler.startQuery(0, null, uri, PROJECTION, selection, 
+                    null /* selection args */, CalendarAlerts.DEFAULT_SORT_ORDER);
+        } else {
+            mCursor.requery();
+        }
+    }
+    
+    @Override
+    protected void onStop() {
+        super.onStop();
+        AlertReceiver.updateAlertNotification(this);
+        
+        if (mCursor != null) {
+            mCursor.deactivate();
+        }
+    }
+    
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mCursor != null) {
+            mCursor.close();
+        }
+    }
+    
+    private OnClickListener mSnoozeAllListener = new OnClickListener() {
+        public void onClick(View v) {
+            NotificationManager nm =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            nm.cancel(NOTIFICATION_ID);
+            mCursor.moveToPosition(-1);
+            while (mCursor.moveToNext()) {
+                long eventId = mCursor.getLong(INDEX_EVENT_ID);
+                long begin = mCursor.getLong(INDEX_BEGIN);
+                long end = mCursor.getLong(INDEX_END);
+                long alarmTime = mCursor.getLong(INDEX_ALARM_TIME);
+
+                // Mark this alarm as DISMISSED
+                mCursor.updateInt(INDEX_STATE, CalendarAlerts.DISMISSED);
+                
+                // Create a new alarm entry in the CalendarAlerts table
+                long now = System.currentTimeMillis();
+                alarmTime = now + SNOOZE_DELAY;
+                
+                // Set the "minutes" to zero to indicate this is a snoozed
+                // alarm.  There is code in AlertService.java that checks
+                // this field.
+                Uri uri = CalendarAlerts.insert(mResolver, eventId,
+                        begin, end, alarmTime, 0 /* minutes */);
+                
+                // Set a new alarm to go off after the snooze delay.
+                Intent intent = new Intent(Calendar.EVENT_REMINDER_ACTION);
+                intent.setData(uri);
+                intent.putExtra(Calendar.EVENT_BEGIN_TIME, begin);
+                intent.putExtra(Calendar.EVENT_END_TIME, end);
+                
+                PendingIntent sender = PendingIntent.getBroadcast(AlertActivity.this,
+                        0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                Object service = getSystemService(Context.ALARM_SERVICE);
+                AlarmManager alarmManager = (AlarmManager) service;
+                alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
+            }
+            mCursor.commitUpdates();
+            finish();
+        }
+    };
+    
+    private OnClickListener mDismissAllListener = new OnClickListener() {
+        public void onClick(View v) {
+            NotificationManager nm =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            nm.cancel(NOTIFICATION_ID);
+            mCursor.moveToPosition(-1);
+            while (mCursor.moveToNext()) {
+                mCursor.updateInt(INDEX_STATE, CalendarAlerts.DISMISSED);
+            }
+            mCursor.commitUpdates();
+            finish();
+        }
+    };
+    
+    public boolean isEmpty() {
+        return (mCursor.getCount() == 0);
+    }
+    
+    public Cursor getItemForView(View view) {
+        int index = mListView.getPositionForView(view);
+        if (index < 0) {
+            return null;
+        }
+        return (Cursor) mListView.getAdapter().getItem(index);
+    }
+}
diff --git a/src/com/android/calendar/AlertAdapter.java b/src/com/android/calendar/AlertAdapter.java
new file mode 100644 (file)
index 0000000..102e9ff
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.PorterDuff;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+public class AlertAdapter extends ResourceCursorAdapter {
+
+    public AlertAdapter(Context context, int resource) {
+        super(context, resource, null);
+    }
+    
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        TextView textView;
+        
+        ImageView stripe = (ImageView) view.findViewById(R.id.vertical_stripe);
+        int color = cursor.getInt(AlertActivity.INDEX_COLOR) & 0xbbffffff;
+        stripe.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        
+        // Repeating info
+        View repeatContainer = view.findViewById(R.id.repeat_icon);
+        String rrule = cursor.getString(AlertActivity.INDEX_RRULE);
+        if (rrule != null) {
+            repeatContainer.setVisibility(View.VISIBLE);
+        } else {
+            repeatContainer.setVisibility(View.GONE);
+        }
+                
+        // Reminder
+        boolean hasAlarm = cursor.getInt(AlertActivity.INDEX_HAS_ALARM) != 0;
+        if (hasAlarm) {
+            AgendaAdapter.updateReminder(view, context, cursor.getLong(AlertActivity.INDEX_BEGIN),
+                    cursor.getLong(AlertActivity.INDEX_EVENT_ID));
+        }
+        
+        String eventName = cursor.getString(AlertActivity.INDEX_TITLE);
+        String location = cursor.getString(AlertActivity.INDEX_EVENT_LOCATION);
+        long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
+        long endMillis = cursor.getLong(AlertActivity.INDEX_END);
+        boolean allDay = cursor.getInt(AlertActivity.INDEX_ALL_DAY) != 0;
+        
+        updateView(context, view, eventName, location, startMillis, endMillis, allDay);
+    }
+    
+    public static void updateView(Context context, View view, String eventName, String location,
+            long startMillis, long endMillis, boolean allDay) {
+        
+        Resources res = context.getResources();
+        TextView textView;
+        
+        // What
+        if (eventName == null || eventName.length() == 0) {
+            eventName = res.getString(R.string.no_title_label);
+        }
+        textView = (TextView) view.findViewById(R.id.event_title);
+        textView.setText(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(context)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        when = DateUtils.formatDateRange(startMillis, endMillis, flags);
+        textView = (TextView) view.findViewById(R.id.when);
+        textView.setText(when);
+        
+        // Where
+        textView = (TextView) view.findViewById(R.id.where);
+        if (location == null || location.length() == 0) {
+            textView.setVisibility(View.GONE);
+        } else {
+            textView.setText(location);
+        }
+    }
+}
diff --git a/src/com/android/calendar/AlertReceiver.java b/src/com/android/calendar/AlertReceiver.java
new file mode 100644 (file)
index 0000000..ea2d549
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.provider.Calendar.CalendarAlerts;
+
+/**
+ * Receives android.intent.action.EVENT_REMINDER intents and handles
+ * event reminders.  The intent URI specifies an alert id in the 
+ * CalendarAlerts database table.  This class also receives the
+ * BOOT_COMPLETED intent so that it can add a status bar notification
+ * if there are Calendar event alarms that have not been dismissed.
+ * It also receives the TIME_CHANGED action so that it can fire off
+ * snoozed alarms that have become ready.  The real work is done in
+ * the AlertService class.
+ */
+public class AlertReceiver extends BroadcastReceiver {
+    
+    private static final String[] ALERT_PROJECTION = new String[] { 
+        CalendarAlerts.TITLE,           // 0
+        CalendarAlerts.EVENT_LOCATION,  // 1
+    };
+    private static final int ALERT_INDEX_TITLE = 0;
+    private static final int ALERT_INDEX_EVENT_LOCATION = 1;
+    
+    private static final String DELETE_ACTION = "delete";
+    
+    private static final String[] PROJECTION = new String[] { 
+        CalendarAlerts._ID,              // 0
+        CalendarAlerts.STATE,            // 1
+    };
+    
+    public static final int INDEX_STATE = 1;
+    
+    static final Object mStartingServiceSync = new Object();
+    static PowerManager.WakeLock mStartingService;
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DELETE_ACTION.equals(intent.getAction())) {
+            
+            /* The user has clicked the "Clear All Notifications"
+             * buttons so dismiss all Calendar alerts.
+             */
+            dismissAllEvents(context);
+        } else {
+            Intent i = new Intent();
+            i.setClass(context, AlertService.class);
+            i.putExtras(intent);
+            i.putExtra("action", intent.getAction());
+            Uri uri = intent.getData();
+            
+            // This intent might be a BOOT_COMPLETED so it might not have a Uri.
+            if (uri != null) {
+                i.putExtra("uri", uri.toString());
+            }
+            beginStartingService(context, i);
+        }
+    }
+    
+    private void dismissAllEvents(Context context) {
+        Uri uri = CalendarAlerts.CONTENT_URI_BY_INSTANCE;
+        String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
+        ContentResolver resolver = context.getContentResolver();
+        Cursor cursor = resolver.query(uri, PROJECTION, selection, null, null);
+        if (cursor != null) {
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                cursor.updateInt(INDEX_STATE, CalendarAlerts.DISMISSED);
+            }
+            cursor.commitUpdates();
+            cursor.close();
+        }
+    }
+
+    /**
+     * Start the service to process the current event notifications, acquiring
+     * the wake lock before returning to ensure that the service will run.
+     */
+    public static void beginStartingService(Context context, Intent intent) {
+        synchronized (mStartingServiceSync) {
+            if (mStartingService == null) {
+                PowerManager pm =
+                    (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+                mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                        "StartingAlertService");
+                mStartingService.setReferenceCounted(false);
+            }
+            mStartingService.acquire();
+            context.startService(intent);
+        }
+    }
+    
+    /**
+     * Called back by the service when it has finished processing notifications,
+     * releasing the wake lock if the service is now stopping.
+     */
+    public static void finishStartingService(Service service, int startId) {
+        synchronized (mStartingServiceSync) {
+            if (mStartingService != null) {
+                if (service.stopSelfResult(startId)) {
+                    mStartingService.release();
+                }
+            }
+        }
+    }
+    
+    public static void updateAlertNotification(Context context) {
+        // This can be called regularly to synchronize the alert notification
+        // with the contents of the CalendarAlerts table.
+        
+        ContentResolver cr = context.getContentResolver();
+        
+        if (cr == null) {
+            return;
+        }
+        
+        String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
+        Cursor alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION, selection, null);
+        
+        NotificationManager nm = 
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        
+        if (alertCursor == null) {
+            nm.cancel(AlertActivity.NOTIFICATION_ID);
+            return;
+        }
+
+        if (!alertCursor.moveToFirst()) {
+            alertCursor.close();
+            nm.cancel(AlertActivity.NOTIFICATION_ID);
+            return;
+        }
+        
+        String title = alertCursor.getString(ALERT_INDEX_TITLE);
+        String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
+        
+        Notification notification = AlertReceiver.makeNewAlertNotification(context, title, 
+                location, alertCursor.getCount());
+        alertCursor.close();
+        
+        nm.notify(0, notification);
+    }
+    
+    public static Notification makeNewAlertNotification(Context context, String title, 
+            String location, int numReminders) {
+        Resources res = context.getResources();
+        
+        // Create an intent triggered by clicking on the status icon.
+        Intent clickIntent = new Intent();
+        clickIntent.setClass(context, AlertActivity.class);
+        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        
+        // Create an intent triggered by clicking on the "Clear All Notifications" button
+        Intent deleteIntent = new Intent();
+        deleteIntent.setClass(context, AlertReceiver.class);
+        deleteIntent.setAction(DELETE_ACTION);
+        
+        if (title == null || title.length() == 0) {
+            title = res.getString(R.string.no_title_label);
+        }
+        
+        String helperString;
+        if (numReminders > 1) {
+            String format;
+            if (numReminders == 2) {
+                format = res.getString(R.string.alert_missed_events_single);
+            } else {
+                format = res.getString(R.string.alert_missed_events_multiple);
+            }
+            helperString = String.format(format, numReminders - 1);
+        } else {
+            helperString = location;
+        }
+        
+        Notification notification = new Notification(
+                R.drawable.stat_notify_calendar,
+                null,
+                System.currentTimeMillis());
+        notification.setLatestEventInfo(context,
+                title,
+                helperString,
+                PendingIntent.getActivity(context, 0, clickIntent, 0));
+        notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
+        
+        return notification;
+    }
+}
+
diff --git a/src/com/android/calendar/AlertService.java b/src/com/android/calendar/AlertService.java
new file mode 100644 (file)
index 0000000..13f808b
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * 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 android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.pim.DateUtils;
+import android.preference.PreferenceManager;
+import android.provider.Calendar;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.CalendarAlerts;
+import android.provider.Calendar.Instances;
+import android.provider.Calendar.Reminders;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+/**
+ * This service is used to handle calendar event reminders.
+ */
+public class AlertService extends Service {
+    private static final String TAG = "AlertService";
+    private static final boolean localLOGV = false || Config.LOGV;
+    
+    private volatile Looper mServiceLooper;
+    private volatile ServiceHandler mServiceHandler;
+    
+    private static final String[] ALERT_PROJECTION = new String[] { 
+        CalendarAlerts._ID,                     // 0
+        CalendarAlerts.EVENT_ID,                // 1
+        CalendarAlerts.STATE,                   // 2
+        CalendarAlerts.TITLE,                   // 3
+        CalendarAlerts.EVENT_LOCATION,          // 4
+        CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
+        CalendarAlerts.ALL_DAY,                 // 6
+        CalendarAlerts.ALARM_TIME,              // 7
+        CalendarAlerts.MINUTES,                 // 8
+        CalendarAlerts.BEGIN,                   // 9
+    };
+
+    // We just need a simple projection that returns any column
+    private static final String[] ALERT_PROJECTION_SMALL = new String[] { 
+        CalendarAlerts._ID,                     // 0
+    };
+    
+    private static final int ALERT_INDEX_ID = 0;
+    private static final int ALERT_INDEX_EVENT_ID = 1;
+    private static final int ALERT_INDEX_STATE = 2;
+    private static final int ALERT_INDEX_TITLE = 3;
+    private static final int ALERT_INDEX_EVENT_LOCATION = 4;
+    private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
+    private static final int ALERT_INDEX_ALL_DAY = 6;
+    private static final int ALERT_INDEX_ALARM_TIME = 7;
+    private static final int ALERT_INDEX_MINUTES = 8;
+    private static final int ALERT_INDEX_BEGIN = 9;
+
+    private String[] INSTANCE_PROJECTION = { Instances.BEGIN, Instances.END };
+    private static final int INSTANCES_INDEX_BEGIN = 0;
+    private static final int INSTANCES_INDEX_END = 1;
+
+    // We just need a simple projection that returns any column
+    private static final String[] REMINDER_PROJECTION_SMALL = new String[] { 
+        Reminders._ID,                     // 0
+    };
+    
+    private void processMessage(Message msg) {
+        Bundle bundle = (Bundle) msg.obj;
+        
+        // On reboot, update the notification bar with the contents of the
+        // CalendarAlerts table.
+        String action = bundle.getString("action");
+        if (action.equals(Intent.ACTION_BOOT_COMPLETED)
+                || action.equals(Intent.ACTION_TIME_CHANGED)) {
+            doTimeChanged();
+            return;
+        }
+
+        // The Uri specifies an entry in the CalendarAlerts table
+        Uri alertUri = Uri.parse(bundle.getString("uri"));
+        if (localLOGV) Log.v(TAG, "uri: " + alertUri);
+
+        ContentResolver cr = getContentResolver();
+        Cursor alertCursor = cr.query(alertUri, ALERT_PROJECTION,
+                null /* selection */, null, null /* sort order */);
+        
+        long alertId, eventId, instanceId, alarmTime;
+        int minutes;
+        String eventName;
+        String location;
+        boolean allDay;
+        boolean declined = false;
+        try {
+            if (alertCursor == null || !alertCursor.moveToFirst()) {
+                // This can happen if the event was deleted.
+                if (localLOGV) Log.v(TAG, "alert not found");
+                return;
+            }
+            alertId = alertCursor.getLong(ALERT_INDEX_ID);
+            eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
+            minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
+            eventName = alertCursor.getString(ALERT_INDEX_TITLE);
+            location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
+            allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
+            alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
+            declined = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS) == 
+                    Attendees.ATTENDEE_STATUS_DECLINED;
+            
+            // If the event was declined, then mark the alarm DISMISSED,
+            // otherwise, mark the alarm FIRED.
+            int newState = CalendarAlerts.FIRED;
+            if (declined) {
+                newState = CalendarAlerts.DISMISSED;
+            }
+            alertCursor.updateInt(ALERT_INDEX_STATE, newState);
+            alertCursor.commitUpdates();
+        } finally {
+            if (alertCursor != null) {
+                alertCursor.close();
+            }
+        }
+        
+        // Do not show an alert if the event was declined
+        if (declined) {
+            if (localLOGV) Log.v(TAG, "event declined, alert cancelled");
+            return;
+        }
+        
+        long beginTime = bundle.getLong(Calendar.EVENT_BEGIN_TIME, 0);
+        long endTime = bundle.getLong(Calendar.EVENT_END_TIME, 0);
+        
+        // Check if this alarm is still valid.  The time of the event may
+        // have been changed, or the reminder may have been changed since
+        // this alarm was set. First, search for an instance in the Instances
+        // that has the same event id and the same begin and end time.
+        // Then check for a reminder in the Reminders table to ensure that
+        // the reminder minutes is consistent with this alarm.
+        String selection = Instances.EVENT_ID + "=" + eventId;
+        Cursor instanceCursor = Instances.query(cr, INSTANCE_PROJECTION,
+                beginTime, endTime, selection, Instances.DEFAULT_SORT_ORDER);
+        long instanceBegin = 0, instanceEnd = 0;
+        try {
+            if (instanceCursor == null || !instanceCursor.moveToFirst()) {
+                // Delete this alarm from the CalendarAlerts table
+                cr.delete(alertUri, null /* selection */, null /* selection args */);
+                if (localLOGV) Log.v(TAG, "instance not found, alert cancelled");
+                return;
+            }
+            instanceBegin = instanceCursor.getLong(INSTANCES_INDEX_BEGIN);
+            instanceEnd = instanceCursor.getLong(INSTANCES_INDEX_END);
+        } finally {
+            if (instanceCursor != null) {
+                instanceCursor.close();
+            }
+        }
+        
+        // Check that a reminder for this event exists with the same number
+        // of minutes.  But snoozed alarms have minutes = 0, so don't do this
+        // check for snoozed alarms.
+        if (minutes > 0) {
+            selection = Reminders.EVENT_ID + "=" + eventId
+                + " AND " + Reminders.MINUTES + "=" + minutes;
+            Cursor reminderCursor = cr.query(Reminders.CONTENT_URI, REMINDER_PROJECTION_SMALL,
+                    selection, null /* selection args */, null /* sort order */);
+            try {
+                if (reminderCursor == null || reminderCursor.getCount() == 0) {
+                    // Delete this alarm from the CalendarAlerts table
+                    cr.delete(alertUri, null /* selection */, null /* selection args */);
+                    if (localLOGV) Log.v(TAG, "reminder not found, alert cancelled");
+                    return;
+                }
+            } finally {
+                if (reminderCursor != null) {
+                    reminderCursor.close();
+                }
+            }
+        }
+        
+        // If the event time was changed and the event has already ended,
+        // then don't sound the alarm.
+        if (alarmTime > instanceEnd) {
+            // Delete this alarm from the CalendarAlerts table
+            cr.delete(alertUri, null /* selection */, null /* selection args */);
+            if (localLOGV) Log.v(TAG, "event ended, alert cancelled");
+            return;
+        }
+
+        // If minutes > 0, then this is a normal alarm (not a snoozed alarm)
+        // so check for duplicate alarms.  A duplicate alarm can occur when
+        // the start time of an event is changed to an earlier time.  The
+        // later alarm (that was first scheduled for the later event time)
+        // should be discarded.
+        long computedAlarmTime = instanceBegin - minutes * DateUtils.MINUTE_IN_MILLIS;
+        if (minutes > 0 && computedAlarmTime != alarmTime) {
+            // If the event time was changed to a later time, then the computed
+            // alarm time is in the future and we shouldn't sound this alarm.
+            if (computedAlarmTime > alarmTime) {
+                // Delete this alarm from the CalendarAlerts table
+                cr.delete(alertUri, null /* selection */, null /* selection args */);
+                if (localLOGV) Log.v(TAG, "event postponed, alert cancelled");
+                return;
+            }
+            
+            // Check for another alarm in the CalendarAlerts table that has the
+            // same event id and the same "minutes".  This can occur
+            // if the event start time was changed to an earlier time and the
+            // alarm for the later time goes off.  To avoid discarding alarms
+            // for repeating events (that have the same event id), we check
+            // that the other alarm fired recently (within an hour of this one).
+            long recently = alarmTime - 60 * DateUtils.MINUTE_IN_MILLIS;
+            selection = CalendarAlerts.EVENT_ID + "=" + eventId
+                    + " AND " + CalendarAlerts.TABLE_NAME + "." + CalendarAlerts._ID
+                    + "!=" + alertId
+                    + " AND " + CalendarAlerts.MINUTES + "=" + minutes
+                    + " AND " + CalendarAlerts.ALARM_TIME + ">" + recently
+                    + " AND " + CalendarAlerts.ALARM_TIME + "<=" + alarmTime;
+            alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION_SMALL, selection, null);
+            if (alertCursor != null) {
+                try {
+                    if (alertCursor.getCount() > 0) {
+                        // Delete this alarm from the CalendarAlerts table
+                        cr.delete(alertUri, null /* selection */, null /* selection args */);
+                        if (localLOGV) Log.v(TAG, "duplicate alarm, alert cancelled");
+                        return;
+                    }
+                } finally {
+                    alertCursor.close();
+                }
+            }
+        }
+        
+        // Find all the alerts that have fired but have not been dismissed
+        selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
+        alertCursor = CalendarAlerts.query(cr, ALERT_PROJECTION, selection, null);
+        
+        if (alertCursor == null || alertCursor.getCount() == 0) {
+            if (localLOGV) Log.v(TAG, "no fired alarms found");
+            return;
+        }
+
+        int numReminders = alertCursor.getCount();
+        try {
+            while (alertCursor.moveToNext()) {
+                long otherEventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
+                long otherAlertId = alertCursor.getLong(ALERT_INDEX_ID);
+                int otherAlarmState = alertCursor.getInt(ALERT_INDEX_STATE);
+                long otherBeginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
+                if (otherEventId == eventId && otherAlertId != alertId
+                        && otherAlarmState == CalendarAlerts.FIRED
+                        && otherBeginTime == beginTime) {
+                    // This event already has an alert that fired and has not
+                    // been dismissed.  This can happen if an event has
+                    // multiple reminders.  Do not count this as a separate
+                    // reminder.  But we do want to sound the alarm and vibrate
+                    // the phone, if necessary.
+                    if (localLOGV) Log.v(TAG, "multiple alarms for this event");
+                    numReminders -= 1;
+                }
+            }
+        } finally {
+            alertCursor.close();
+        }
+        
+        if (localLOGV) Log.v(TAG, "creating new alarm notification, numReminders: " + numReminders);
+        Notification notification = AlertReceiver.makeNewAlertNotification(this, eventName,
+                location, numReminders);
+        
+        // Generate either a pop-up dialog, status bar notification, or
+        // neither. Pop-up dialog and status bar notification may include a
+        // sound, an alert, or both. A status bar notification also includes
+        // a toast.
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        String reminderType = prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_TYPE,
+                CalendarPreferenceActivity.ALERT_TYPE_STATUS_BAR);
+        
+        if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_OFF)) {
+            if (localLOGV) Log.v(TAG, "alert preference is OFF");
+            return;
+        }
+        
+        NotificationManager nm = 
+            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        boolean reminderVibrate = 
+                prefs.getBoolean(CalendarPreferenceActivity.KEY_ALERTS_VIBRATE, false);
+        String reminderRingtone =
+                prefs.getString(CalendarPreferenceActivity.KEY_ALERTS_RINGTONE, null);
+
+        // Possibly generate a vibration
+        if (reminderVibrate) {
+            notification.defaults |= Notification.DEFAULT_VIBRATE;
+        }
+        
+        // Possibly generate a sound.  If 'Silent' is chosen, the ringtone string will be empty.
+        notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
+                .parse(reminderRingtone);
+        
+        if (reminderType.equals(CalendarPreferenceActivity.ALERT_TYPE_ALERTS)) {
+            Intent alertIntent = new Intent();
+            alertIntent.setClass(this, AlertActivity.class);
+            alertIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(alertIntent);
+        } else {
+            LayoutInflater inflater;
+            inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.alert_toast, null);
+            
+            AlertAdapter.updateView(this, view, eventName, location, beginTime, endTime, allDay);
+        }
+        nm.notify(0, notification);
+    }
+    
+    private void doTimeChanged() {
+        ContentResolver cr = getContentResolver();
+        Object service = getSystemService(Context.ALARM_SERVICE);
+        AlarmManager manager = (AlarmManager) service;
+        CalendarAlerts.rescheduleMissedAlarms(cr, this, manager);
+        AlertReceiver.updateAlertNotification(this);
+    }
+    
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+        
+        @Override
+        public void handleMessage(Message msg) {
+            processMessage(msg);
+            // NOTE: We MUST not call stopSelf() directly, since we need to
+            // make sure the wake lock acquired by AlertReceiver is released.
+            AlertReceiver.finishStartingService(AlertService.this, msg.arg1);
+        } 
+    };
+
+    @Override
+    public void onCreate() {
+        HandlerThread thread = new HandlerThread("AlertService",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        msg.obj = intent.getExtras();
+        mServiceHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void onDestroy() {
+        mServiceLooper.quit();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/src/com/android/calendar/CalendarActivity.java b/src/com/android/calendar/CalendarActivity.java
new file mode 100644 (file)
index 0000000..a99ba61
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * 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.accounts.AccountMonitor;
+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.pim.Time;
+import android.provider.Calendar;
+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.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;
+
+    protected static final String BUNDLE_KEY_RESTORE_TIME = "key_restore_time";
+
+    private ContentResolver mContentResolver;
+
+    private AccountMonitor mAccountMonitor;
+
+    protected ProgressBar mProgressBar;
+    protected ViewSwitcher mViewSwitcher;
+    protected Animation mInAnimationForward;
+    protected Animation mOutAnimationForward;
+    protected Animation mInAnimationBackward;
+    protected Animation mOutAnimationBackward;
+    EventLoader mEventLoader;
+
+    Time mSelectedDay = new Time();
+
+    /* 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);
+
+        // 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
+        long oldHeapSize = 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(new CalendarGestureListener());
+        mEventLoader = new EventLoader(this);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+
+        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
+        Time time = new Time();
+        time.set(savedInstanceState.getLong(BUNDLE_KEY_RESTORE_TIME));
+        view.setSelectedDay(time);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mEventLoader.startBackgroundThread();
+        eventsChanged();
+
+        // 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 onDestroy() {
+        if (mAccountMonitor != null) {
+            mAccountMonitor.close();
+        }
+        super.onDestroy();
+    }
+
+    @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) {
+        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());
+        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) {
+        long offset = 0;
+        if (xOffSet != 0) {
+
+            // The user might have scrolled the view to the left or right
+            // in which case we just want to animate the bit left over
+            // instead of animating all of it. So calculate how much
+            // it's been moved already and animate the remaining portion
+            double progress = ((width - (Math.abs(xOffSet))) / width);
+            long duration = mInAnimationForward.getDuration();
+            offset = -1 * (long) (duration - (duration * progress));
+        }
+        if (forward) {
+            mInAnimationForward.setStartOffset(offset);
+            mOutAnimationForward.setStartOffset(offset);
+            mViewSwitcher.setInAnimation(mInAnimationForward);
+            mViewSwitcher.setOutAnimation(mOutAnimationForward);
+        } else {
+            mInAnimationBackward.setStartOffset(offset);
+            mOutAnimationBackward.setStartOffset(offset);
+            mViewSwitcher.setInAnimation(mInAnimationBackward);
+            mViewSwitcher.setOutAnimation(mOutAnimationBackward);
+        }
+        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 onShowPress(MotionEvent ev) {
+            CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
+            view.doShowPress(ev);
+        }
+
+        @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
new file mode 100644 (file)
index 0000000..a3dad95
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.Application;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+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).
+     */
+    /* package */ 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();
+
+        /*
+         * Ensure the default values are set for any receiver, activity,
+         * service, etc. of Calendar
+         */
+        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+    }
+
+}
diff --git a/src/com/android/calendar/CalendarData.java b/src/com/android/calendar/CalendarData.java
new file mode 100644 (file)
index 0000000..49f3056
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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 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",
+        "9", "10", "11", "12" };
+
+    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/CalendarPreferenceActivity.java b/src/com/android/calendar/CalendarPreferenceActivity.java
new file mode 100644 (file)
index 0000000..78d6065
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.CheckBoxPreference;
+import android.preference.RingtonePreference;
+
+public class CalendarPreferenceActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
+    // 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_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";
+    
+    // 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;
+    CheckBoxPreference mVibrate;
+    RingtonePreference mRingtone;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        // 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);
+        mVibrate = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE);
+        mRingtone = (RingtonePreference) preferenceScreen.findPreference(KEY_ALERTS_RINGTONE);
+        
+        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)) {
+            mVibrate.setChecked(false);
+            mVibrate.setEnabled(false);
+            mRingtone.setEnabled(false);
+        } else {
+            mVibrate.setEnabled(true);
+            mRingtone.setEnabled(true);
+        }
+    }
+}
diff --git a/src/com/android/calendar/CalendarView.java b/src/com/android/calendar/CalendarView.java
new file mode 100644 (file)
index 0000000..9126d6e
--- /dev/null
@@ -0,0 +1,2999 @@
+/*
+ * 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.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.Paint.Style;
+import android.graphics.Path.Direction;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.util.Log;
+import android.view.ContextMenu;
+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.ContextMenu.ContextMenuInfo;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/**
+ * 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 boolean mOnFlingCalled;
+
+    protected CalendarApplication mCalendarApp;
+    protected CalendarActivity mParentActivity;
+
+    private static final String[] CALENDARS_PROJECTION = new String[] {
+        Calendars._ID,          // 0
+        Calendars.ACCESS_LEVEL, // 1
+    };
+    private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
+    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 final 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 HORIZONTAL_SCROLL_THRESHOLD = 50;
+
+    private ContinueScroll mContinueScroll = new ContinueScroll();
+
+    // Make this visible within the package for more informative debugging
+    Time mBaseDate;
+
+    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 Drawable mBoxNormal;
+    private Drawable mBoxSelected;
+    private Drawable mBoxPressed;
+    private Drawable mBoxLongPressed;
+    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;
+    private boolean mLaunchNewView;
+
+    // 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 mEventPaint = new Paint();
+    private Paint mSelectionPaint = new Paint();
+    private Path mPath = new Path();
+
+    protected boolean mDrawTextInEventRect;
+    private int mStartDay;
+
+    private PopupWindow mPopup;
+    private View mPopupView;
+    private static final int POPUP_HEIGHT = 62;
+
+    // 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 final int SINGLE_ALLDAY_HEIGHT = 20;
+    private static final int MAX_ALLDAY_HEIGHT = 72;
+    private static final int ALLDAY_TOP_MARGIN = 3;
+    private static final 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;
+
+    /* 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 final int NORMAL_FONT_SIZE = 12;
+    private static final int EVENT_TEXT_FONT_SIZE = 12;
+    private static final int HOURS_FONT_SIZE = 12;
+    private static final int AMPM_FONT_SIZE = 9;
+    private static final int MIN_CELL_WIDTH_FOR_TEXT = 10;
+    private static final int MAX_EVENT_TEXT_LEN = 500;
+    private static final float MIN_EVENT_HEIGHT = 15.0F;  // in pixels
+
+    private static final float CALENDAR_COLOR_WIDTH = 8.0F;
+    private static final float CALENDAR_COLOR_HEIGHT_OFFSET = 6.0F;
+
+    private static int mSelectionColor;
+    private static int mAllDayEventColor;
+
+    private int mViewStartX;
+    private int mViewStartY;
+    private int mMaxViewStartY;
+    private int mBitmapHeight;
+    private int mViewHeight;
+    private int mViewWidth;
+    private int mGridAreaHeight;
+    private int mGridAreaWidth;
+    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;
+
+    public CalendarView(CalendarActivity activity) {
+        super(activity);
+        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 */);
+
+        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 = Calendar.getInstance().getFirstDayOfWeek();
+        if (mStartDay == Calendar.SATURDAY) {
+            mStartDay = Time.SATURDAY;
+        } else if (mStartDay == Calendar.MONDAY) {
+            mStartDay = Time.MONDAY;
+        } else {
+            mStartDay = Time.SUNDAY;
+        }
+
+        mSelectionColor = mResources.getColor(R.color.selection);
+        mAllDayEventColor = mResources.getColor(R.color.calendar_all_day_event_color);
+        int eventTextColor = mResources.getColor(R.color.calendar_event_text_color);
+        mEventPaint.setColor(eventTextColor);
+        mEventPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
+        mEventPaint.setTextAlign(Paint.Align.LEFT);
+        mEventPaint.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);
+
+        // 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);
+            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);
+        mIs24HourFormat = DateFormat.is24HourFormat(context);
+        mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
+        mHoursWidth = computeMaxStringWidth(0, mHourStrs, p);
+
+        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;
+        mBoxNormal = mResources.getDrawable(R.drawable.box_appointment_normal);
+        mBoxSelected = mResources.getDrawable(R.drawable.box_appointment_selected);
+        mBoxPressed = mResources.getDrawable(R.drawable.box_appointment_pressed);
+        mBoxLongPressed = mResources.getDrawable(R.drawable.box_appointment_longpress);
+
+        LayoutInflater inflater;
+        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPopupView = inflater.inflate(R.layout.bubble_event, null);
+        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();
+        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 */);
+        }
+    }
+
+    /**
+     * 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 */);
+            }
+        }
+
+        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);
+        mFirstDate = mBaseDate.monthDay;
+
+        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(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.
+    }
+
+    void setDetailedView(String detailedView) {
+        mDetailedView = detailedView;
+    }
+
+    @Override
+    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+        mViewWidth = width;
+        mViewHeight = height;
+        mGridAreaWidth = width - mHoursWidth;
+        mCellWidth = (mGridAreaWidth - (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);
+        mPopup.dismiss();
+        mPopup.setWidth(eventAreaWidth - 20);
+        mPopup.setHeight(POPUP_HEIGHT);
+    }
+
+    /**
+     * 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();
+        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(mContext, 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(mContext, 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(mContext, 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();
+                    MenuHelper.switchTo(mParentActivity, mDetailedView, millis);
+                    mParentActivity.finish();
+                }
+            }
+        } 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(mContext, 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(mContext, 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;
+        }
+        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 = false;
+        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();
+
+            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:
+            mPopup.dismiss();
+            mParentActivity.finish();
+            return true;
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextLeft;
+            }
+            if (mSelectedEvent == null) {
+                selectionDay -= 1;
+            }
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextRight;
+            }
+            if (mSelectedEvent == null) {
+                selectionDay += 1;
+            }
+            redraw = true;
+            break;
+
+        case KeyEvent.KEYCODE_DPAD_UP:
+            if (mSelectedEvent != null) {
+                mSelectedEvent = mSelectedEvent.nextUp;
+            }
+            if (mSelectedEvent == null) {
+                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) {
+                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();
+        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();
+        }
+    }
+
+    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(mResources.getColor(R.color.calendar_hour_background));
+        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(mResources.getColor(R.color.calendar_date_banner_background));
+        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(mResources.getColor(R.color.calendar_grid_area_background));
+        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(mResources.getColor(R.color.calendar_date_selected));
+                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;
+        }
+
+        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(mResources.getColor(R.color.calendar_ampm_label));
+        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 doDraw(Canvas canvas) {
+        Paint p = mPaint;
+        Rect r = mRect;
+
+        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);
+            x += deltaX;
+        }
+    }
+
+    private void drawHours(Rect r, Canvas canvas, Paint p) {
+        // Draw the background for the hour labels
+        p.setColor(mResources.getColor(R.color.calendar_hour_background));
+        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(mResources.getColor(R.color.calendar_grid_area_background));
+        canvas.drawRect(r, p);
+
+        // Draw a highlight on the selected hour (if needed)
+        if (mSelectionMode != SELECTION_HIDDEN && !mSelectionAllDay) {
+            p.setColor(mResources.getColor(R.color.calendar_hour_selected));
+            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(mResources.getColor(R.color.calendar_grid_area_selected));
+            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(mResources.getColor(R.color.calendar_hour_label));
+        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;
+
+        p.setTypeface(mBold);
+        p.setAntiAlias(true);
+
+        boolean isWeekend = false;
+        if ((mStartDay == Time.SUNDAY && (day == 0 || day == 6))
+                || (mStartDay == Time.MONDAY && (day == 5 || day == 6))
+                || (mStartDay == Time.SATURDAY && (day == 0 || day == 1))) {
+            isWeekend = true;
+        }
+
+        if (isWeekend) {
+            p.setColor(mResources.getColor(R.color.week_weekend));
+        } else {
+            p.setColor(mResources.getColor(R.color.calendar_date_banner_text_color));
+        }
+
+        int dateNum = mFirstDate + day;
+        if (dateNum > mMonthLength) {
+            dateNum -= mMonthLength;
+        }
+
+        // Add a leading zero if the date is a single digit
+        if (dateNum < 10) {
+            dateStr += " 0" + dateNum;
+        } else {
+            dateStr += " " + dateNum;
+        }
+
+        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(mResources.getColor(R.color.calendar_grid_area_background));
+        r.top = 0;
+        r.bottom = mBitmapHeight;
+        r.left = 0;
+        r.right = mViewWidth;
+        canvas.drawRect(r, p);
+
+        // Draw the horizontal grid lines
+        p.setColor(mResources.getColor(R.color.calendar_grid_line_horizontal_color));
+        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(mResources.getColor(R.color.calendar_grid_line_vertical_color));
+        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 eventPaint = mEventPaint;
+
+        // 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(mResources.getColor(R.color.calendar_all_day_background));
+        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(mResources.getColor(R.color.calendar_grid_area_background));
+        canvas.drawRect(r, p);
+
+        // Draw the vertical grid lines
+        p.setColor(mResources.getColor(R.color.calendar_grid_line_vertical_color));
+        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);
+            drawEventText(event, rf, canvas, eventPaint, 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);
+                drawEventText(event, rf, canvas, eventPaint, 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) {
+        // 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);
+        } else {
+            // Use the normal color for all-day events
+            p.setColor(mAllDayEventColor);
+        }
+
+        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);
+
+        // Draw the calendar color inset rectangle
+        p.setColor(event.color);
+
+        // Save the outer rectangle coordinates so that we can restore them
+        float right = rf.right;
+        float top = rf.top;
+        float bottom = rf.bottom;
+
+        rf.right = rf.left + CALENDAR_COLOR_WIDTH;
+        float eventHeight = rf.bottom - rf.top;
+        rf.top += 0.05f * eventHeight;
+        rf.bottom -= 0.05f * eventHeight;
+        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+        // Change the rf coordinates to be the area suitable for text.
+        rf.left = rf.right;
+        rf.right = right;
+        rf.top = top;
+        rf.bottom = bottom;
+        return rf;
+    }
+
+    private void drawEvents(int date, int left, int top, Canvas canvas, Paint p) {
+        Paint eventPaint = mEventPaint;
+        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);
+            drawEventText(event, rf, canvas, eventPaint, NORMAL_TEXT_TOP_MARGIN);
+        }
+
+        if (date == mSelectionDay && !mSelectionAllDay && isFocused()
+                && mSelectionMode != SELECTION_HIDDEN) {
+            computeNeighbors();
+            if (mSelectedEvent != null) {
+                RectF rf = drawEventRect(mSelectedEvent, canvas, p);
+                drawEventText(mSelectedEvent, rf, canvas, eventPaint, 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 = 0;
+        int prevBottom = 0;
+        int prevLeft = 0;
+        int prevRight = 0;
+        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(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) {
+        Drawable box = mBoxNormal;
+
+        // 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;
+            } else if (mSelectionMode == SELECTION_SELECTED) {
+                // Also, remember the last selected event that we drew
+                mPrevSelectedEvent = event;
+                box = mBoxSelected;
+            } else if (mSelectionMode == SELECTION_LONGPRESS) {
+                box = mBoxLongPressed;
+            }
+        }
+
+        RectF rf = mRectF;
+        rf.top = event.top;
+        rf.bottom = event.bottom;
+        rf.left = event.left;
+        rf.right = event.right;
+        int boxTop = (int) event.top;
+        int boxBottom = (int) event.bottom;
+        int boxLeft = (int) event.left;
+        int boxRight = (int) event.right;
+
+        box.setBounds(boxLeft, boxTop, boxRight, boxBottom);
+        box.draw(canvas);
+
+        // Save the coordinates
+        float eventRight = rf.right;
+        float eventTop = rf.top;
+        float eventBottom = rf.bottom;
+
+        // Draw the calendar color as a small rectangle on top of the event
+        // rectangle.  Use a fixed size width unless it doesn't fit, in which
+        // case use 1/2 the width.  For the height, use a fixed offset from
+        // the top and bottom unless that would be too small, in which case,
+        // use a 5% offset for top and bottom.
+        float width = CALENDAR_COLOR_WIDTH;
+        float maxWidth = (rf.right - rf.left) / 2.0f;
+        if (width > maxWidth) {
+            width = maxWidth;
+        }
+
+        // The drawable has a 1-pixel border so we need to shift the
+        // inner colored rectangle by one pixel.  But we don't shift by 1
+        // if the rectangle is really small.
+        if (width >= 3) {
+            rf.left += 1;
+        }
+        float top = rf.top + CALENDAR_COLOR_HEIGHT_OFFSET;
+        float bottom = rf.bottom - CALENDAR_COLOR_HEIGHT_OFFSET;
+        float height = bottom - top;
+        if (height < MIN_EVENT_HEIGHT) {
+            float eventHeight = rf.bottom - rf.top;
+            top = rf.top + 0.2f * eventHeight;
+            bottom = rf.bottom - 0.2f * eventHeight;
+        }
+        rf.right = rf.left + width;
+        rf.top = top;
+        rf.bottom = bottom;
+        p.setColor(event.color);
+        canvas.drawRoundRect(rf, SMALL_ROUND_RADIUS, SMALL_ROUND_RADIUS, p);
+
+        // Set the rectangle for the event text.
+        rf.left = rf.right;
+        rf.right = eventRight;
+        rf.top = eventTop;
+        rf.bottom = eventBottom;
+        return rf;
+    }
+
+    private void drawEventText(Event event, RectF rf, Canvas canvas, Paint p, int topMargin) {
+        if (mDrawTextInEventRect == false) {
+            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();
+        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;
+        }
+
+        // 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 = DateUtils.formatDateRange(event.startMillis, event.endMillis, flags);
+        TextView timeView = (TextView) mPopupView.findViewById(R.id.time);
+        timeView.setText(timeRange);
+
+        TextView whereView = (TextView) mPopupView.findViewById(R.id.where);
+        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.
+
+    void doDown(MotionEvent ev) {
+        mTouchMode = TOUCH_MODE_DOWN;
+        mViewStartX = 0;
+        mOnFlingCalled = false;
+        mLaunchNewView = false;
+        getHandler().removeCallbacks(mContinueScroll);
+    }
+
+    void doSingleTapUp(MotionEvent ev) {
+        mSelectionMode = SELECTION_SELECTED;
+        mRedrawScreen = true;
+        invalidate();
+        if (mLaunchNewView) {
+            mLaunchNewView = false;
+            switchViews(false /* not the trackball */);
+        }
+    }
+
+    void doShowPress(MotionEvent ev) {
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        Event selectedEvent = mSelectedEvent;
+        int selectedDay = mSelectionDay;
+        int selectedHour = mSelectionHour;
+
+        boolean validPosition = setSelectionFromPosition(x, y);
+        if (!validPosition) {
+            return;
+        }
+
+        mSelectionMode = SELECTION_PRESSED;
+        mRedrawScreen = true;
+        invalidate();
+
+        // If the tap is on an already selected event or hour slot,
+        // then launch a new view.  Otherwise, just select the event.
+        if (selectedEvent != null && selectedEvent == mSelectedEvent) {
+            // Launch the "View event" view when the finger lifts up,
+            // unless the finger moves before lifting up.
+            mLaunchNewView = true;
+        } else if (selectedEvent == null && selectedDay == mSelectionDay
+                && selectedHour == mSelectionHour) {
+            // Launch the Day/Agenda view when the finger lifts up,
+            // unless the finger moves before lifting up.
+            mLaunchNewView = true;
+        }
+    }
+
+    void doLongPress(MotionEvent ev) {
+        mLaunchNewView = false;
+        mSelectionMode = SELECTION_LONGPRESS;
+        mRedrawScreen = true;
+        invalidate();
+        performLongClick();
+    }
+
+    void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
+        mLaunchNewView = false;
+        // 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);
+        int selectionDay;
+        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.setFrame(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);
+                } 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();
+        }
+
+        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);
+
+                if (isEventEditable(mContext, mSelectedEvent)) {
+                    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');
+
+                    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);
+
+                if (isEventEditable(mContext, mSelectedEvent)) {
+                    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');
+
+                    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.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.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.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.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(mContext, 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(mContext, 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();
+                    MenuHelper.switchTo(mParentActivity, DayActivity.class.getName(), startMillis);
+                    mParentActivity.finish();
+                    break;
+                }
+                case MenuHelper.MENU_AGENDA: {
+                    long startMillis = getSelectedTimeInMillis();
+                    MenuHelper.switchTo(mParentActivity, AgendaActivity.class.getName(), startMillis);
+                    mParentActivity.finish();
+                    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(mContext, 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 boolean isEventEditable(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) || (cursor.getCount() == 0)) {
+            return false;
+        }
+        cursor.moveToFirst();
+        long calId = cursor.getLong(0);
+        cursor.deactivate();
+
+        Uri uri = Calendars.CONTENT_URI;
+        String where = String.format(CALENDARS_WHERE, calId);
+        cursor = cr.query(uri, CALENDARS_PROJECTION, where, null, null);
+
+        if (cursor != null) {
+            cursor.moveToFirst();
+            visibility = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL);
+            cursor.close();
+        }
+
+        // Attendees cursor
+        uri = Attendees.CONTENT_URI;
+        where = String.format(ATTENDEES_WHERE, e.id);
+        Cursor attendeesCursor = cr.query(uri, ATTENDEES_PROJECTION, where, null, null);
+        if (attendeesCursor != null) {
+            if (attendeesCursor.moveToFirst()) {
+                relationship = attendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP);
+            }
+        }
+        attendeesCursor.close();
+
+        if (visibility >= Calendars.CONTRIBUTOR_ACCESS &&
+                relationship >= Attendees.RELATIONSHIP_ORGANIZER) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 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(mResources, 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.
+     */
+    public void cleanup() {
+        // Protect against null-pointer exceptions
+        if (mPopup != null) {
+            mPopup.dismiss();
+        }
+        Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeCallbacks(mDismissPopup);
+        }
+    }
+
+    @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();
+            }
+        }
+    }
+}
+
diff --git a/src/com/android/calendar/DateSpinner.java b/src/com/android/calendar/DateSpinner.java
new file mode 100644 (file)
index 0000000..34cc7d5
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * 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.DatePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.content.Context;
+import android.pim.Time;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.Spinner;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/**
+ * The DateSpinner class is a {@link Spinner} widget that pops up a
+ * {@link DatePickerDialog} when clicked (instead of the usual menu of
+ * options for the Spinner).  This class also provides a callback
+ * {@link DateSpinner.OnDateChangedListener} when the date is changed
+ * either through the Spinner or through the DatePickerDialog.
+ */
+public class DateSpinner extends Spinner {
+
+    /**
+     * The listener interface for providing a callback when the date is
+     * changed by the user.
+     */
+    public interface OnDateChangedListener {
+        /**
+         * This method is called when the user changes the date through
+         * the Spinner or the DatePickerDialog.
+         * 
+         * @param dateSpinner the DateSpinner object that changed
+         * @param millis the date in UTC milliseconds
+         */
+        public void dateChanged(DateSpinner dateSpinner, long millis);
+    }
+
+    private Context mContext;
+    
+    // mTime and mMillis must be kept in sync
+    private Time mTime = new Time();
+    private long mMillis;
+    private int mWeekStartDay = Calendar.SUNDAY;
+    private OnDateChangedListener mOnDateChangedListener;
+    
+    // The default number of spinner choices is 2 weeks worth of days
+    // surrounding the given date.
+    private static final int NUM_SPINNER_CHOICES = 15;
+    
+    // The array of spinner choices, in UTC milliseconds.
+    private long[] mSpinnerMillis;
+    
+    // The minimum millisecond spinner value.  The DateSpinner can automatically
+    // generate an array of spinner choices for the dates.  This variable
+    // prevents the spinner choices from being less than this date (specified
+    // in UTC milliseconds).
+    private long mMinimumMillis;
+    
+    // The number of spinner choices.  This may be set by the user of this
+    // widget.
+    private int mNumSpinnerChoices;
+    
+    public DateSpinner(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    public DateSpinner(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+    }
+
+    public DateSpinner(Context context,
+                  AttributeSet attrs,
+                  int defStyle) {
+        super(context, attrs, defStyle);
+        mContext = context;
+    }
+    
+    private OnDateSetListener mDateSetListener = new OnDateSetListener() {
+        // This is called by the DatePickerDialog when the user sets the date.
+        public void onDateSet(DatePicker view, int year, int month, int day) {
+            mTime.year = year;
+            mTime.month = month;
+            mTime.monthDay = day;
+            mMillis = mTime.normalize(true /* ignore isDst */);
+            createSpinnerElements();
+            if (mOnDateChangedListener != null) {
+                mOnDateChangedListener.dateChanged(DateSpinner.this, mMillis);
+            }
+        }
+    };
+    
+    private OnItemSelectedListener mItemSelectedListener = new OnItemSelectedListener() {
+        // This is called when the user changes the selection in the Spinner.
+        public void onItemSelected(AdapterView parent, View v, int position, long id) {
+            long millis = mSpinnerMillis[position];
+            if (millis == mMillis) {
+                return;
+            }
+            mMillis = millis;
+            mTime.set(millis);
+            if (mOnDateChangedListener != null) {
+                mOnDateChangedListener.dateChanged(DateSpinner.this, millis);
+            }
+        }
+    
+        public void onNothingSelected(AdapterView parent) {
+        }
+    };
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+        case KeyEvent.KEYCODE_ENTER:
+            new DatePickerDialog(mContext, mDateSetListener, mTime.year,
+                    mTime.month, mTime.monthDay).show();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public void setMillis(long millis) {
+        mTime.set(millis);
+        mMillis = millis;
+        createSpinnerElements();
+    }
+    
+    public long getMillis() {
+        return mMillis;
+    }
+    
+    private void createSpinnerElements() {
+        // Create spinner elements for a week preceding this date plus a
+        // week following this date.
+        Time time = new Time();
+        time.set(mTime);
+        long millis = time.toMillis(false /* use isDst */);
+        long selectedDay = Time.getJulianDay(millis, time.gmtoff);
+        int numSpinnerChoices = NUM_SPINNER_CHOICES;
+        if (mNumSpinnerChoices > 0) {
+            numSpinnerChoices = mNumSpinnerChoices;
+        }
+        time.monthDay -= numSpinnerChoices / 2;
+        millis = time.normalize(true /* ignore isDst */);
+        if (millis < mMinimumMillis) {
+            long days = (mMinimumMillis - millis) / CalendarView.MILLIS_PER_DAY;
+            millis = mMinimumMillis;
+            time.set(millis);
+        }
+
+        int selectedIndex = 0;
+        ArrayList<Long> millisList = new ArrayList<Long>();
+        for (int pos = 0; pos < numSpinnerChoices; ++pos) {
+            millis = time.normalize(true /* ignore isDst */);
+            int julianDay = Time.getJulianDay(millis, time.gmtoff);
+            if (julianDay == selectedDay) {
+                selectedIndex = pos;
+            }
+            millisList.add(millis);
+            time.monthDay += 1;
+        }
+
+        // Convert the ArrayList to a long[] array.
+        int len = millisList.size();
+        long[] spinnerMillis = new long[len];
+        for (int pos = 0; pos < len; pos++) {
+            spinnerMillis[pos] = millisList.get(pos);
+        }
+        
+        setSpinnerElements(spinnerMillis, selectedIndex);
+    }
+    
+    public void setSpinnerElements(long[] spinnerMillis, int selectedIndex) {
+        if (spinnerMillis == null || spinnerMillis.length == 0) {
+            return;
+        }
+        mSpinnerMillis = spinnerMillis;
+        long millis = spinnerMillis[selectedIndex];
+        mTime.set(millis);
+        mMillis = millis;
+        
+        Time time = new Time();
+        int len = spinnerMillis.length;
+        String[] choices = new String[len];
+        for (int pos = 0; pos < len; pos++) {
+            millis = spinnerMillis[pos];
+            time.set(millis);
+            choices[pos] = Utils.formatDayDate(time, true);
+        }
+        
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
+                android.R.layout.simple_spinner_item, choices);
+        setAdapter(adapter);
+        setSelection(selectedIndex);
+        setOnItemSelectedListener(mItemSelectedListener);
+    }
+
+    public int getYear() {
+        return mTime.year;
+    }
+
+    public int getMonth() {
+        return mTime.month;
+    }
+
+    public int getMonthDay() {
+        return mTime.monthDay;
+    }
+    
+    /**
+     * Fills in the given {@link Time} object with the year, month, and
+     * monthDay from the DateSpinner.
+     * 
+     * @param time the given Time object, allocated by the caller
+     */
+    public void getDate(Time time) {
+        time.year = mTime.year;
+        time.month = mTime.month;
+        time.monthDay = mTime.monthDay;
+    }
+
+    public void setWeekStartDay(int weekStartDay) {
+        mWeekStartDay = weekStartDay;
+    }
+
+    public int getWeekStartDay() {
+        return mWeekStartDay;
+    }
+
+    public void setOnDateChangedListener(OnDateChangedListener onDateChangedListener) {
+        mOnDateChangedListener = onDateChangedListener;
+    }
+
+    public OnDateChangedListener getOnDateChangedListener() {
+        return mOnDateChangedListener;
+    }
+
+    public void setMinimum(long minimum) {
+        mMinimumMillis = minimum;
+    }
+
+    public long getMinimum() {
+        return mMinimumMillis;
+    }
+
+    public void setNumSpinnerChoices(int numSpinnerChoices) {
+        mNumSpinnerChoices = numSpinnerChoices;
+    }
+
+    public int getNumSpinnerChoices() {
+        return mNumSpinnerChoices;
+    }
+}
diff --git a/src/com/android/calendar/DayActivity.java b/src/com/android/calendar/DayActivity.java
new file mode 100644 (file)
index 0000000..51f965b
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+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);
+
+        // Record Day View as the (new) default detailed view.
+        String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.DAY_VIEW_ID];
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(CalendarPreferenceActivity.KEY_DETAILED_VIEW, activityString);
+
+        // Record Day View as the (new) start view
+        editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
+        editor.commit();
+    }
+
+    public View makeView() {
+        DayView view = new DayView(this);
+        view.setId(VIEW_ID);
+        view.setLayoutParams(new ViewSwitcher.LayoutParams(
+                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        view.setSelectedDay(mSelectedDay);
+        return view;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        CalendarView view = (CalendarView) mViewSwitcher.getCurrentView();
+        mSelectedDay = view.getSelectedDay();
+    }
+}
diff --git a/src/com/android/calendar/DayView.java b/src/com/android/calendar/DayView.java
new file mode 100644 (file)
index 0000000..a24b51b
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 DayView extends CalendarView {
+    private static final int CELL_MARGIN = 10;
+
+    public DayView(CalendarActivity activity) {
+        super(activity);
+        init();
+    }
+
+    private void init() {
+        mDrawTextInEventRect = true;
+        mNumDays = 1;
+        mEventGeometry.setCellMargin(CELL_MARGIN);
+    }
+}
diff --git a/src/com/android/calendar/DeleteEventHelper.java b/src/com/android/calendar/DeleteEventHelper.java
new file mode 100644 (file)
index 0000000..7f958b7
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * 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 android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.net.Uri;
+import android.pim.EventRecurrence;
+import android.pim.Time;
+import android.provider.Calendar;
+import android.provider.Calendar.Events;
+
+/**
+ * A helper class for deleting events.  If a normal event is selected for
+ * deletion, then this pops up a confirmation dialog.  If the user confirms,
+ * then the normal event is deleted.
+ * 
+ * <p>
+ * If a repeating event is selected for deletion, then this pops up dialog
+ * asking if the user wants to delete just this one instance, or all the
+ * events in the series, or this event plus all following events.  The user
+ * may also cancel the delete.
+ * </p>
+ * 
+ * <p>
+ * To use this class, create an instance, passing in the parent activity
+ * and a boolean that determines if the parent activity should exit if the
+ * event is deleted.  Then to use the instance, call one of the
+ * {@link delete()} methods on this class.
+ * 
+ * An instance of this class may be created once and reused (by calling
+ * {@link #delete()} multiple times).
+ */
+public class DeleteEventHelper {
+    
+    private final Activity mParent;
+    private final ContentResolver mContentResolver;
+    
+    private long mStartMillis;
+    private long mEndMillis;
+    private Cursor mCursor;
+    
+    /**
+     * If true, then call finish() on the parent activity when done.
+     */
+    private boolean mExitWhenDone;
+    
+    /**
+     * 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;
+    
+    private int mWhichDelete;
+
+    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;
+    
+    public DeleteEventHelper(Activity parent, boolean exitWhenDone) {
+        mParent = parent;
+        mContentResolver = mParent.getContentResolver();
+        mExitWhenDone = exitWhenDone;
+    }
+    
+    public void setExitWhenDone(boolean exitWhenDone) {
+        mExitWhenDone = exitWhenDone;
+    }
+
+    /**
+     * This callback is used when a normal event is deleted.
+     */
+    private DialogInterface.OnClickListener mDeleteNormalDialogListener =
+            new DialogInterface.OnClickListener() {
+        public void onClick(DialogInterface dialog, int button) {
+            long id = mCursor.getInt(mEventIndexId);
+            Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
+            mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
+            if (mExitWhenDone) {
+                mParent.finish();
+            }
+        }
+    };
+
+    /**
+     * This callback is used when a list item for a repeating event is selected
+     */
+    private DialogInterface.OnClickListener mDeleteListListener =
+            new DialogInterface.OnClickListener() {
+        public void onClick(DialogInterface dialog, int button) {
+            mWhichDelete = button;
+        }
+    };
+
+    /**
+     * This callback is used when a repeating event is deleted.
+     */
+    private DialogInterface.OnClickListener mDeleteRepeatingDialogListener =
+            new DialogInterface.OnClickListener() {
+        public void onClick(DialogInterface dialog, int button) {
+            if (mWhichDelete != -1) {
+                deleteRepeatingEvent(mWhichDelete);
+            }
+        }
+    };
+    
+    /**
+     * Does the required processing for deleting an event, which includes
+     * first popping up a dialog asking for confirmation (if the event is
+     * a normal event) or a dialog asking which events to delete (if the
+     * event is a repeating event).  The "which" parameter is used to check
+     * the initial selection and is only used for repeating events.  Set
+     * "which" to -1 to have nothing selected initially.
+     * 
+     * @param begin the begin time of the event, in UTC milliseconds
+     * @param end the end time of the event, in UTC milliseconds
+     * @param eventId the event id
+     * @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, long eventId, int which) {
+        Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, eventId);
+        Cursor cursor = mParent.managedQuery(uri, EVENT_PROJECTION, null, null);
+        if (cursor == null) {
+            return;
+        }
+        cursor.moveToFirst();
+        delete(begin, end, cursor, 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.
+     * The required fields for a normal event are:
+     * 
+     * <ul>
+     *   <li> Events._ID </li>
+     *   <li> Events.TITLE </li>
+     *   <li> Events.RRULE </li>
+     * </ul>
+     * 
+     * The required fields for a repeating event include the above plus the
+     * following fields:
+     * 
+     * <ul>
+     *   <li> Events.ALL_DAY </li>
+     *   <li> Events.CALENDAR_ID </li>
+     *   <li> Events.DTSTART </li>
+     *   <li> Events._SYNC_ID </li>
+     *   <li> Events.EVENT_TIMEZONE </li>
+     * </ul>
+     * 
+     * @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) {
+        mWhichDelete = which;
+        mStartMillis = begin;
+        mEndMillis = end;
+        mCursor = cursor;
+        mEventIndexId = mCursor.getColumnIndexOrThrow(Events._ID);
+        mEventIndexRrule = mCursor.getColumnIndexOrThrow(Events.RRULE);
+        
+        // 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);
+        if (rRule == null) {
+            // This is a normal event. Pop up a confirmation dialog.
+            new AlertDialog.Builder(mParent)
+            .setTitle(R.string.delete_title)
+            .setMessage(R.string.delete_this_event_title)
+            .setIcon(android.R.drawable.ic_dialog_alert)
+            .setPositiveButton(R.string.ok_label, mDeleteNormalDialogListener)
+            .setNegativeButton(R.string.cancel_label, null)
+            .show();
+        } else {
+            // This is a repeating event.  Pop up a dialog asking which events
+            // to delete.
+            new AlertDialog.Builder(mParent)
+            .setTitle(R.string.delete_title)
+            .setIcon(android.R.drawable.ic_dialog_alert)
+            .setSingleChoiceItems(R.array.delete_repeating_labels, which, mDeleteListListener)
+            .setPositiveButton(R.string.ok_label, mDeleteRepeatingDialogListener)
+            .setNegativeButton(R.string.cancel_label, null)
+            .show();
+        }
+    }
+    
+    private void deleteRepeatingEvent(int which) {
+        int indexDtstart = mCursor.getColumnIndexOrThrow(Events.DTSTART);
+        int indexSyncId = mCursor.getColumnIndexOrThrow(Events._SYNC_ID);
+        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);
+
+        String rRule = mCursor.getString(mEventIndexRrule);
+        boolean allDay = mCursor.getInt(indexAllDay) != 0;
+        long dtstart = mCursor.getLong(indexDtstart);
+        long id = mCursor.getInt(mEventIndexId);
+
+        switch (which) {
+            case DELETE_SELECTED:
+            {
+                // Create a recurrence exception by creating a new event
+                // with the status "cancelled".
+                ContentValues values = new ContentValues();
+                
+                // 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);
+                values.put(Events.TITLE, title);
+                
+                String syncId = mCursor.getString(indexSyncId);
+                String timezone = mCursor.getString(indexTimezone);
+                int calendarId = mCursor.getInt(indexCalendarId);
+                values.put(Events.EVENT_TIMEZONE, timezone);
+                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.STATUS, Events.STATUS_CANCELED);
+                
+                mContentResolver.insert(Events.CONTENT_URI, values);
+                break;
+            }
+            case DELETE_ALL: {
+                Uri uri = ContentUris.withAppendedId(Calendar.Events.CONTENT_URI, id);
+                mContentResolver.delete(uri, null /* where */, null /* selectionArgs */);
+                break;
+            }
+            case DELETE_ALL_FOLLOWING: {
+                // If we are deleting the first event in the series and all
+                // 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 */);
+                    break;
+                }
+                
+                // Modify the repeating event to end just before this event time
+                EventRecurrence eventRecurrence = new EventRecurrence();
+                eventRecurrence.parse(rRule);
+                Time date = new Time();
+                if (allDay) {
+                    date.timezone = Time.TIMEZONE_UTC;
+                }
+                date.set(mStartMillis);
+                date.second--;
+                date.normalize(false);
+                
+                // Google calendar seems to require the UNTIL string to be
+                // in UTC.
+                date.switchTimezone(Time.TIMEZONE_UTC);
+                eventRecurrence.until = date.format2445();
+                
+                ContentValues values = new ContentValues();
+                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);
+                break;
+            }
+        }
+        if (mExitWhenDone) {
+            mParent.finish();
+        }
+    }
+}
diff --git a/src/com/android/calendar/EditEvent.java b/src/com/android/calendar/EditEvent.java
new file mode 100644 (file)
index 0000000..8c11974
--- /dev/null
@@ -0,0 +1,1456 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.EventRecurrence;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.text.TextUtils;
+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.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.ResourceCursorAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+
+public class EditEvent extends Activity implements View.OnClickListener {
+    /**
+     * 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
+    };
+    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 String[] CALENDARS_PROJECTION = new String[] {
+            Calendars._ID,          // 0
+            Calendars.DISPLAY_NAME, // 1
+            Calendars.TIMEZONE,     // 2
+    };
+    private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+    private static final int CALENDARS_INDEX_TIMEZONE = 2;
+    private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
+            Calendars.CONTRIBUTOR_ACCESS;
+
+    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 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 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 CheckBox mAllDayCheckBox;
+    private Spinner mCalendarsSpinner;
+    private Spinner mRepeatsSpinner;
+    private Spinner mAvailabilitySpinner;
+    private Spinner mVisibilitySpinner;
+    private TextView mTitleTextView;
+    private TextView mLocationTextView;
+    private TextView mDescriptionTextView;
+    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 EventRecurrence mEventRecurrence = new EventRecurrence();
+    private String mRrule;
+    private ContentValues mInitialValues;
+
+    /**
+     * 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 int mModification = MODIFY_UNINITIALIZED;
+    private int mDefaultReminderMinutes;
+
+    private DeleteEventHelper mDeleteEventHelper;
+
+    /* 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;
+                endMillis = endTime.normalize(true);
+            } else {
+                // The end time was changed.
+                startMillis = startTime.toMillis(true);
+                endTime.hour = hourOfDay;
+                endTime.minute = minute;
+                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(mEndDateButton, endMillis);
+            setTime(mStartTimeButton, startMillis);
+            setTime(mEndTimeButton, endMillis);
+        }
+    }
+
+    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
+        }
+    }
+
+    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();
+        }
+    }
+
+    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) {
+            TextView name = (TextView) view.findViewById(R.id.calendar_name);
+            name.setText(cursor.getString(CALENDARS_INDEX_DISPLAY_NAME));
+        }
+    }
+
+    // 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) {
+            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();
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.edit_event);
+
+        mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
+
+        mStartTime = new Time();
+        mEndTime = new Time();
+
+        Intent intent = getIntent();
+        mUri = intent.getData();
+
+        if (mUri != null) {
+            mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
+        }
+
+        long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
+        long end = intent.getLongExtra(EVENT_END_TIME, 0);
+
+        boolean allDay = false;
+        if (mEventCursor != null) {
+            // The event already exists so fetch the all-day status
+            mEventCursor.moveToFirst();
+            allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+            String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
+            String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
+            
+            // 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);
+            mInitialValues.put(Events.RRULE, rrule);
+            mInitialValues.put(Events.EVENT_TIMEZONE, timezone);
+        } else {
+            // We are creating a new event, so set the default from the
+            // intent (if specified).
+            allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
+        }
+
+        // If the event is all-day, read the times in UTC timezone
+        if (begin != 0) {
+            if (allDay) {
+                String tz = mStartTime.timezone;
+                mStartTime.timezone = Time.TIMEZONE_UTC;
+                mStartTime.set(begin);
+                mStartTime.timezone = tz;
+
+                // Calling normalize to calculate isDst
+                mStartTime.normalize(true);
+            } else {
+                mStartTime.set(begin);
+            }
+        }
+
+        if (end != 0) {
+            if (allDay) {
+                String tz = mStartTime.timezone;
+                mEndTime.timezone = Time.TIMEZONE_UTC;
+                mEndTime.set(end);
+                mEndTime.timezone = tz;
+
+                // Calling normalize to calculate isDst
+                mEndTime.normalize(true);
+            } else {
+                mEndTime.set(end);
+            }
+        }
+
+        mCalendarsCursor = managedQuery(Calendars.CONTENT_URI, CALENDARS_PROJECTION,
+                CALENDARS_WHERE, null);
+
+        // cache all the widgets
+        mTitleTextView = (TextView) findViewById(R.id.title);
+        mLocationTextView = (TextView) findViewById(R.id.location);
+        mDescriptionTextView = (TextView) findViewById(R.id.description);
+        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);
+        mAllDayCheckBox = (CheckBox) findViewById(R.id.is_all_day);
+        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.reminders_container);
+        mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
+
+        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);
+                } 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);
+                }
+            }
+        });
+
+        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 = PreferenceManager.getDefaultSharedPreferences(this);
+        String durationString =
+                prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
+        mDefaultReminderMinutes = Integer.parseInt(durationString);
+
+        // Reminders cursor
+        boolean hasAlarm = (mEventCursor != null)
+                && (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0);
+        if (hasAlarm) {
+            Uri uri = Reminders.CONTENT_URI;
+            long eventId = mEventCursor.getLong(EVENT_INDEX_ID);
+            String where = String.format(REMINDERS_WHERE, eventId);
+            ContentResolver cr = getContentResolver();
+            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();
+
+        mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // populate the calendars spinner
+        mCalendarsSpinner = (Spinner) findViewById(R.id.calendars);
+        CalendarsAdapter adapter = new CalendarsAdapter(this, mCalendarsCursor);
+        mCalendarsSpinner.setAdapter(adapter);
+
+        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);
+            long calendarId = cursor.getLong(EVENT_INDEX_CALENDAR_ID);
+            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) {
+                    items = new CharSequence[2];
+                } else {
+                    items = new CharSequence[3];
+                    items[itemIndex++] = getText(R.string.modify_event);
+                }
+                items[itemIndex++] = getText(R.string.modify_all);
+                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);
+                                } else {
+                                    // We could allow changing the Rrule for
+                                    // all following instances but we'll
+                                    // keep it simple for now.
+                                    mRepeatsSpinner.setEnabled(false);
+                                }
+                            }
+                        })
+                        .show();
+            }
+
+            mTitleTextView.setText(title);
+            mLocationTextView.setText(location);
+            mDescriptionTextView.setText(description);
+            mAvailabilitySpinner.setSelection(availability);
+            mVisibilitySpinner.setSelection(visibility);
+
+            // If there is a calendarId set, move the spinner to the proper
+            // position and hide the spinner, since this is an existing event.
+            if (calendarId != -1) {
+                int count = adapter.getCount();
+                for (int pos = 0 ; pos < count ; pos++) {
+                    long rowID = adapter.getItemId(pos);
+                    if (rowID == calendarId) {
+                        mCalendarsSpinner.setSelection(pos);
+                    }
+                }
+            }
+            View calendarSeparator = findViewById(R.id.calendar_separator);
+            View calendarLabel = findViewById(R.id.calendar_label);
+            calendarSeparator.setVisibility(View.GONE);
+            calendarLabel.setVisibility(View.GONE);
+            mCalendarsSpinner.setVisibility(View.GONE);
+        } else 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 && 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);
+        } else {
+            // New event - set the default reminder
+            if (mDefaultReminderMinutes != 0) {
+                addReminder(this, this, mReminderItems, mReminderValues,
+                        mReminderLabels, mDefaultReminderMinutes);
+            }
+
+            // Hide delete button
+            mDeleteButton.setVisibility(View.GONE);
+        }
+
+        updateRemindersVisibility();
+        populateWhen();
+        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);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case MENU_ADD_REMINDER:
+            // 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();
+            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 boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                // 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()) {
+                    save();
+                }
+                break;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    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 populateRepeats() {
+        Time time = mStartTime;
+        Resources r = getResources();
+        int resource = android.R.layout.simple_spinner_item;
+
+        String[] days = r.getStringArray(R.array.day_labels);
+        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.formatDateRange(when, 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();
+        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;
+        view.setText(DateUtils.formatDateRange(millis, millis, flags));
+    }
+
+    private void setTime(TextView view, long millis) {
+        int flags = DateUtils.FORMAT_SHOW_TIME;
+        if (DateFormat.is24HourFormat(this)) {
+            flags |= DateUtils.FORMAT_24HOUR;
+        }
+        view.setText(DateUtils.formatDateRange(millis, millis, flags));
+    }
+
+    private void save() {
+        // Avoid saving if the calendars cursor is empty. This shouldn't ever
+        // happen since the setup wizard should ensure the user has a calendar.
+        if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0) {
+            Log.w("Cal", "The calendars table does not contain any calendars. New event was not "
+                    + "created.");
+            return;
+        }
+
+        ContentResolver cr = getContentResolver();
+        ContentValues values = getContentValuesFromUi();
+        Uri uri = mUri;
+
+        // For recurring events, we must make sure that we use duration rather
+        // than dtend.
+        if (uri == null) {
+            // Create new event with new contents
+            addRecurrenceRule(values);
+            uri = cr.insert(Events.CONTENT_URI, values);
+
+        } else if (mRrule == null) {
+            // Modify contents of a non-repeating event
+            addRecurrenceRule(values);
+            checkTimeDependentFields(values);
+            cr.update(uri, values, null, null);
+            
+        } 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);
+            cr.update(uri, values, null, null);
+
+        } 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);
+
+            uri = cr.insert(Events.CONTENT_URI, values);
+
+        } else if (mModification == MODIFY_ALL_FOLLOWING) {
+            // Modify contents of all future instances of repeating event
+
+            // Update the current repeating event to end at the new start time
+            updatePastEvents(cr, uri);
+
+            // Create a new event that has a begin time of now
+            mEventRecurrence.parse(mRrule);
+            addRecurrenceRule(values);
+            values.remove(Events.DTEND);
+            uri = cr.insert(Events.CONTENT_URI, values);
+
+        } else if (mModification == MODIFY_ALL) {
+            
+            // Modify all instances of repeating event
+            addRecurrenceRule(values);
+            
+            if (mRrule == null) {
+                
+                // We've changed a recurring event to non recurring
+                // End the previous events and create a new event
+                // If we're the first even though we just delete and
+                // create a new one.
+                if (isFirstEventInSeries()) {
+                    cr.delete(uri, null, null);
+                } else {
+                    updatePastEvents(cr, uri);
+                }
+                uri = cr.insert(Events.CONTENT_URI, values);
+            } else {
+                checkTimeDependentFields(values);
+                values.remove(Events.DTEND);
+                cr.update(uri, values, null, null);
+            }
+        }
+
+        if (uri != null) {
+            long eventId = ContentUris.parseId(uri);
+            ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
+                    mReminderValues);
+            saveReminders(cr, eventId, reminderMinutes, mOriginalMinutes);
+        }
+    }
+
+    private boolean isFirstEventInSeries() {
+        int dtStart = mEventCursor.getColumnIndexOrThrow(Events.DTSTART);
+        long start = mEventCursor.getLong(dtStart);
+        return start == mStartTime.toMillis(true);
+    }
+
+    private void updatePastEvents(ContentResolver cr, Uri uri) {
+        long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
+        String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION);
+
+        Time oldUntilTime = new Time();
+        long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+        if (mInitialValues.getAsBoolean(Events.ALL_DAY)) {
+            oldUntilTime.timezone = Time.TIMEZONE_UTC;
+        }
+        oldUntilTime.set(begin);
+        oldUntilTime.second--;
+        oldUntilTime.normalize(false);
+        mEventRecurrence.until = oldUntilTime.format2445();
+
+        ContentValues oldValues = new ContentValues();
+        oldValues.put(Events.DTSTART, oldStartMillis);
+        oldValues.put(Events.DURATION, oldDuration);
+        oldValues.put(Events.RRULE, mEventRecurrence.toString());
+        cr.update(uri, oldValues, null, null);
+    }
+
+    private void checkTimeDependentFields(ContentValues values) {
+        long oldBegin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+        long oldEnd = mInitialValues.getAsLong(EVENT_END_TIME);
+        boolean oldAllDay = mInitialValues.getAsBoolean(Events.ALL_DAY);
+        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) == 1;
+        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;
+    }
+
+    static void saveReminders(ContentResolver cr, long eventId,
+            ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes) {
+        // If the reminders have not changed, then don't update the database
+        if (reminderMinutes.equals(originalMinutes)) {
+            return;
+        }
+
+        // Delete all the existing reminders for this event
+        String where = Reminders.EVENT_ID + "=?";
+        String[] args = new String[] { Long.toString(eventId) };
+        cr.delete(Reminders.CONTENT_URI, where, args);
+
+        // Update the "hasAlarm" field for the event
+        ContentValues values = new ContentValues();
+        int len = reminderMinutes.size();
+        values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
+        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        cr.update(uri, values, null /* where */, null /* selection args */);
+
+        // 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);
+            cr.insert(Reminders.CONTENT_URI, values);
+        }
+    }
+
+    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 updateRecurrenceRule() {
+        int position = mRepeatsSpinner.getSelectedItemPosition();
+        int selection = mRecurrenceIndexes.get(position);
+
+        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();
+        boolean isAllDay = mAllDayCheckBox.isChecked();
+        String location = mLocationTextView.getText().toString();
+        String description = mDescriptionTextView.getText().toString();
+        long calendarId = mCalendarsSpinner.getSelectedItemId();
+        Cursor calendarCursor = (Cursor) mCalendarsSpinner.getSelectedItem();
+
+        ContentValues values = new ContentValues();
+
+        String timezone = null;
+        long startMillis;
+        long endMillis;
+        if (isAllDay) {
+            // Reset start and end time, increment the monthDay by 1, and set
+            // the timezone to UTC, as required for all-day events.
+            timezone = Time.TIMEZONE_UTC;
+            mStartTime.hour = 0;
+            mStartTime.minute = 0;
+            mStartTime.second = 0;
+            mStartTime.timezone = timezone;
+            startMillis = mStartTime.normalize(true);
+
+            mEndTime.hour = 0;
+            mEndTime.minute = 0;
+            mEndTime.second = 0;
+            mEndTime.monthDay++;
+            mEndTime.timezone = timezone;
+            endMillis = mEndTime.normalize(true);
+        } else {
+            startMillis = mStartTime.toMillis(true);
+            endMillis = mEndTime.toMillis(true);
+            if (mEventCursor != null) {
+                timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
+            } else if (calendarCursor != null) {
+                timezone = calendarCursor.getString(CALENDARS_INDEX_TIMEZONE);
+            }
+        }
+
+        values.put(Events.EVENT_TIMEZONE, timezone);
+        values.put(Events.CALENDAR_ID, calendarId);
+        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();
+        if (title.length() > 0) {
+            return false;
+        }
+
+        String location = mLocationTextView.getText().toString();
+        if (location.length() > 0) {
+            return false;
+        }
+
+        String description = mDescriptionTextView.getText().toString();
+        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/Event.java b/src/com/android/calendar/Event.java
new file mode 100644 (file)
index 0000000..d1551bb
--- /dev/null
@@ -0,0 +1,650 @@
+/*
+ * 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.res.Resources;
+import android.database.Cursor;
+import android.os.Debug;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+import android.provider.Calendar.Attendees;
+import android.provider.Calendar.Instances;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+// TODO: should Event be Parcelable so it can be passed via Intents?
+public class Event implements Comparable, Cloneable {
+
+    private static final boolean PROFILE = false;
+
+    private static final String[] PROJECTION = new String[] {
+            Instances.TITLE,           // 0
+            Instances.EVENT_LOCATION,  // 1
+            Instances.ALL_DAY,         // 2
+            Instances.COLOR,           // 3
+            Instances.EVENT_TIMEZONE,  // 4
+            Instances.EVENT_ID,        // 5
+            Instances.BEGIN,           // 6
+            Instances.END,             // 7
+            Instances._ID,             // 8
+            Instances.START_DAY,       // 9
+            Instances.END_DAY,         // 10
+            Instances.START_MINUTE,    // 11
+            Instances.END_MINUTE,      // 12
+            Instances.HAS_ALARM,       // 13
+            Instances.RRULE,           // 14
+            Instances.RDATE,           // 15
+    };
+
+    // The indices for the projection array above.
+    private static final int PROJECTION_TITLE_INDEX = 0;
+    private static final int PROJECTION_LOCATION_INDEX = 1;
+    private static final int PROJECTION_ALL_DAY_INDEX = 2;
+    private static final int PROJECTION_COLOR_INDEX = 3;
+    private static final int PROJECTION_TIMEZONE_INDEX = 4;
+    private static final int PROJECTION_EVENT_ID_INDEX = 5;
+    private static final int PROJECTION_BEGIN_INDEX = 6;
+    private static final int PROJECTION_END_INDEX = 7;
+    private static final int PROJECTION_START_DAY_INDEX = 9;
+    private static final int PROJECTION_END_DAY_INDEX = 10;
+    private static final int PROJECTION_START_MINUTE_INDEX = 11;
+    private static final int PROJECTION_END_MINUTE_INDEX = 12;
+    private static final int PROJECTION_HAS_ALARM_INDEX = 13;
+    private static final int PROJECTION_RRULE_INDEX = 14;
+    private static final int PROJECTION_RDATE_INDEX = 15;
+
+    public long id;
+    public int color;
+    public CharSequence title;
+    public CharSequence location;
+    public boolean allDay;
+
+    public int startDay;       // start Julian day
+    public int endDay;         // end Julian day
+    public int startTime;      // Start and end time are in minutes since midnight
+    public int endTime;
+
+    public long startMillis;   // UTC milliseconds since the epoch
+    public long endMillis;     // UTC milliseconds since the epoch
+    private int mColumn;
+    private int mMaxColumns;
+
+    public boolean hasAlarm;
+    public boolean isRepeating;
+
+    // The coordinates of the event rectangle drawn on the screen.
+    public float left;
+    public float right;
+    public float top;
+    public float bottom;
+
+    // These 4 fields are used for navigating among events within the selected
+    // hour in the Day and Week view.
+    public Event nextRight;
+    public Event nextLeft;
+    public Event nextUp;
+    public Event nextDown;
+
+    private static final int MIDNIGHT_IN_MINUTES = 24 * 60;
+
+    @Override
+    public final Object clone() {
+        Event e = new Event();
+
+        e.title = title;
+        e.color = color;
+        e.location = location;
+        e.allDay = allDay;
+        e.startDay = startDay;
+        e.endDay = endDay;
+        e.startTime = startTime;
+        e.endTime = endTime;
+        e.startMillis = startMillis;
+        e.endMillis = endMillis;
+        e.hasAlarm = hasAlarm;
+        e.isRepeating = isRepeating;
+
+        return e;
+    }
+
+    public final void copyTo(Event dest) {
+        dest.id = id;
+        dest.title = title;
+        dest.color = color;
+        dest.location = location;
+        dest.allDay = allDay;
+        dest.startDay = startDay;
+        dest.endDay = endDay;
+        dest.startTime = startTime;
+        dest.endTime = endTime;
+        dest.startMillis = startMillis;
+        dest.endMillis = endMillis;
+        dest.hasAlarm = hasAlarm;
+        dest.isRepeating = isRepeating;
+    }
+
+    public static final Event newInstance() {
+        Event e = new Event();
+
+        e.id = 0;
+        e.title = null;
+        e.color = 0;
+        e.location = null;
+        e.allDay = false;
+        e.startDay = 0;
+        e.endDay = 0;
+        e.startTime = 0;
+        e.endTime = 0;
+        e.startMillis = 0;
+        e.endMillis = 0;
+        e.hasAlarm = false;
+        e.isRepeating = false;
+
+        return e;
+    }
+
+    /**
+     * Compares this event to the given event.  This is just used for checking
+     * if two events differ.  It's not used for sorting anymore.
+     */
+    public final int compareTo(Object obj) {
+        Event e = (Event) obj;
+
+        // The earlier start day and time comes first
+        if (startDay < e.startDay) return -1;
+        if (startDay > e.startDay) return 1;
+        if (startTime < e.startTime) return -1;
+        if (startTime > e.startTime) return 1;
+
+        // The later end time comes first (in order to put long strips on
+        // the left).
+        if (endDay < e.endDay) return 1;
+        if (endDay > e.endDay) return -1;
+        if (endTime < e.endTime) return 1;
+        if (endTime > e.endTime) return -1;
+
+        // Sort all-day events before normal events.
+        if (allDay && !e.allDay) return -1;
+        if (!allDay && e.allDay) return 1;
+
+        // If two events have the same time range, then sort them in
+        // alphabetical order based on their titles.
+        int cmp = compareStrings(title, e.title);
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        // If the titles are the same then compare the other fields
+        // so that we can use this function to check for differences
+        // between events.
+        cmp = compareStrings(location, e.location);
+        if (cmp != 0) {
+            return cmp;
+        }
+        return 0;
+    }
+
+    /**
+     * Compare string a with string b, but if either string is null,
+     * then treat it (the null) as if it were the empty string ("").
+     *
+     * @param a the first string
+     * @param b the second string
+     * @return the result of comparing a with b after replacing null
+     *  strings with "".
+     */
+    private int compareStrings(CharSequence a, CharSequence b) {
+        String aStr, bStr;
+        if (a != null) {
+            aStr = a.toString();
+        } else {
+            aStr = "";
+        }
+        if (b != null) {
+            bStr = b.toString();
+        } else {
+            bStr = "";
+        }
+        return aStr.compareTo(bStr);
+    }
+
+    /**
+     * Loads <i>days</i> days worth of instances starting at <i>start</i>.
+     */
+    public static void loadEvents(Context context, ArrayList<Event> events,
+            long start, int days, int requestId, AtomicInteger sequenceNumber) {
+
+        if (PROFILE) {
+            Debug.startMethodTracing("loadEvents");
+        }
+
+        Cursor c = null;
+
+        events.clear();
+        try {
+            Time local = new Time();
+            int count;
+
+            local.set(start);
+            int startDay = Time.getJulianDay(start, local.gmtoff);
+            int endDay = startDay + days;
+
+            local.monthDay += days;
+            long end = local.normalize(true /* ignore isDst */);
+
+            // 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 below.
+            //
+            // The sort order is: events with an earlier start time occur
+            // first and if the start times are the same, then events with
+            // a later end time occur first. The later end time is ordered
+            // first so that long rectangles in the calendar views appear on
+            // the left side.  If the start and end times of two events are
+            // the same then we sort alphabetically on the title.  This isn't
+            // required for correctness, it just adds a nice touch.
+
+            String orderBy = Instances.SORT_CALENDAR_VIEW;
+
+            // Respect the preference to show/hide declined events
+            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+            boolean hideDeclined = prefs.getBoolean(CalendarPreferenceActivity.KEY_HIDE_DECLINED,
+                    false);
+
+            String where = null;
+            if (hideDeclined) {
+                where = Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
+            }
+
+            c = Instances.query(context.getContentResolver(), PROJECTION,
+                    start - DateUtils.DAY_IN_MILLIS, end + DateUtils.DAY_IN_MILLIS, where, orderBy);
+
+            if (c == null) {
+                Log.e("Cal", "loadEvents() returned null cursor!");
+                return;
+            }
+
+            // Check if we should return early because there are more recent
+            // load requests waiting.
+            if (requestId != sequenceNumber.get()) {
+                return;
+            }
+
+            count = c.getCount();
+
+            if (count == 0) {
+                return;
+            }
+
+            Resources res = context.getResources();
+            while (c.moveToNext()) {
+                Event e = new Event();
+
+                e.id = c.getLong(PROJECTION_EVENT_ID_INDEX);
+                e.title = c.getString(PROJECTION_TITLE_INDEX);
+                e.location = c.getString(PROJECTION_LOCATION_INDEX);
+                e.allDay = c.getInt(PROJECTION_ALL_DAY_INDEX) != 0;
+                String timezone = c.getString(PROJECTION_TIMEZONE_INDEX);
+
+                if (e.title == null || e.title.length() == 0) {
+                    e.title = res.getString(R.string.no_title_label);
+                }
+
+                if (!c.isNull(PROJECTION_COLOR_INDEX)) {
+                    // Read the color from the database
+                    e.color = c.getInt(PROJECTION_COLOR_INDEX);
+                } else {
+                    e.color = res.getColor(R.color.event_center);
+                }
+
+                long eStart = c.getLong(PROJECTION_BEGIN_INDEX);
+                long eEnd = c.getLong(PROJECTION_END_INDEX);
+
+                e.startMillis = eStart;
+                e.startTime = c.getInt(PROJECTION_START_MINUTE_INDEX);
+                e.startDay = c.getInt(PROJECTION_START_DAY_INDEX);
+
+                e.endMillis = eEnd;
+                e.endTime = c.getInt(PROJECTION_END_MINUTE_INDEX);
+                e.endDay = c.getInt(PROJECTION_END_DAY_INDEX);
+
+                if (e.startDay > endDay || e.endDay < startDay) {
+                    continue;
+                }
+
+                e.hasAlarm = c.getInt(PROJECTION_HAS_ALARM_INDEX) != 0;
+
+                // Check if this is a repeating event
+                String rrule = c.getString(PROJECTION_RRULE_INDEX);
+                String rdate = c.getString(PROJECTION_RDATE_INDEX);
+                if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
+                    e.isRepeating = true;
+                } else {
+                    e.isRepeating = false;
+                }
+
+                events.add(e);
+            }
+
+            computePositions(events);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+            if (PROFILE) {
+                Debug.stopMethodTracing();
+            }
+        }
+    }
+
+    /**
+     * Computes a position for each event.  Each event is displayed
+     * as a non-overlapping rectangle.  For normal events, these rectangles
+     * are displayed in separate columns in the week view and day view.  For
+     * all-day events, these rectangles are displayed in separate rows along
+     * the top.  In both cases, each event is assigned two numbers: N, and
+     * Max, that specify that this event is the Nth event of Max number of
+     * events that are displayed in a group. The width and position of each
+     * rectangle depend on the maximum number of rectangles that occur at
+     * the same time.
+     *
+     * @param eventsList the list of events, sorted into increasing time order
+     */
+    static void computePositions(ArrayList<Event> eventsList) {
+        if (eventsList == null)
+            return;
+
+        // Compute the column positions separately for the all-day events
+        doComputePositions(eventsList, false);
+        doComputePositions(eventsList, true);
+        if (false) {
+            // Create a numbered log because adb logcat duplicates old entries
+            // at random times and this makes it hard to compare two different
+            // runs.  We can post-process the numbered log using sort and uniq.
+            int logIndex = 0;
+            for (Event e : eventsList) {
+                if (!e.allDay) continue;
+                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
+                | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+        String timeRange = DateUtils.formatDateRange(e.startMillis,
+                        e.endMillis, flags);
+                Log.i("Cal", logIndex + " allDay: " + e.allDay
+                                + " days: " + e.startDay + "," + e.endDay
+                                + " times: " + e.startTime + "," + e.endTime
+                                + " " + timeRange
+                                + " nth/max: " + e.getColumn() + "/" + e.getMaxColumns()
+                                + " "  + e.title);
+                logIndex += 1;
+            }
+            for (Event e : eventsList) {
+                if (e.allDay) continue;
+                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
+                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+                String timeRange = DateUtils.formatDateRange(e.startMillis,
+                                e.endMillis, flags);
+                Log.i("Cal", logIndex + " allDay: " + e.allDay
+                                + " days: " + e.startDay + "," + e.endDay
+                                + " times: " + e.startTime + "," + e.endTime
+                                + " " + timeRange
+                                + " nth/max: " + e.getColumn() + "/" + e.getMaxColumns()
+                                + " "  + e.title);
+                logIndex += 1;
+            }
+        }
+    }
+
+    private static void doComputePositions(ArrayList<Event> eventsList,
+            boolean doAllDayEvents) {
+        ArrayList<Event> activeList = new ArrayList<Event>();
+        ArrayList<Event> groupList = new ArrayList<Event>();
+
+        long colMask = 0;
+        int maxCols = 0;
+        for (Event event : eventsList) {
+            // Process all-day events separately
+            if (event.allDay != doAllDayEvents)
+                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 is less than or equal to
+            // the current event's start time.
+            Iterator<Event> iter = activeList.iterator();
+            while (iter.hasNext()) {
+                Event active = iter.next();
+                if (active.getEndMillis() <= 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();
+                }
+            }
+
+            // If the active list is empty, then reset the max columns, clear
+            // the column bit mask, and empty the groupList.
+            if (activeList.isEmpty()) {
+                for (Event ev : groupList) {
+                    ev.setMaxColumns(maxCols);
+                }
+                maxCols = 0;
+                colMask = 0;
+                groupList.clear();
+            }
+
+            // Find the first empty column.  Empty columns are represented by
+            // zero bits in the column mask "colMask".
+            int col = findFirstZeroBit(colMask);
+            if (col == 64)
+                col = 63;
+            colMask |= (1L << col);
+            event.setColumn(col);
+            activeList.add(event);
+            groupList.add(event);
+            int len = activeList.size();
+            if (maxCols < len)
+                maxCols = len;
+        }
+        for (Event ev : groupList) {
+            ev.setMaxColumns(maxCols);
+        }
+    }
+
+    public static int findFirstZeroBit(long val) {
+        for (int ii = 0; ii < 64; ++ii) {
+            if ((val & (1L << ii)) == 0)
+                return ii;
+        }
+        return 64;
+    }
+
+    /**
+     * Returns a darker version of the given color.  It does this by dividing
+     * each of the red, green, and blue components by 2.  The alpha value is
+     * preserved.
+     */
+    private static final int getDarkerColor(int color) {
+        int darker = (color >> 1) & 0x007f7f7f;
+        int alpha = color & 0xff000000;
+        return alpha | darker;
+    }
+
+    // For testing. This method can be removed at any time.
+    private static ArrayList<Event> createTestEventList() {
+        ArrayList<Event> evList = new ArrayList<Event>();
+        createTestEvent(evList, 1, 5, 10);
+        createTestEvent(evList, 2, 5, 10);
+        createTestEvent(evList, 3, 15, 20);
+        createTestEvent(evList, 4, 20, 25);
+        createTestEvent(evList, 5, 30, 70);
+        createTestEvent(evList, 6, 32, 40);
+        createTestEvent(evList, 7, 32, 40);
+        createTestEvent(evList, 8, 34, 38);
+        createTestEvent(evList, 9, 34, 38);
+        createTestEvent(evList, 10, 42, 50);
+        createTestEvent(evList, 11, 45, 60);
+        createTestEvent(evList, 12, 55, 90);
+        createTestEvent(evList, 13, 65, 75);
+
+        createTestEvent(evList, 21, 105, 130);
+        createTestEvent(evList, 22, 110, 120);
+        createTestEvent(evList, 23, 115, 130);
+        createTestEvent(evList, 24, 125, 140);
+        createTestEvent(evList, 25, 127, 135);
+
+        createTestEvent(evList, 31, 150, 160);
+        createTestEvent(evList, 32, 152, 162);
+        createTestEvent(evList, 33, 153, 163);
+        createTestEvent(evList, 34, 155, 170);
+        createTestEvent(evList, 35, 158, 175);
+        createTestEvent(evList, 36, 165, 180);
+
+        return evList;
+    }
+
+    // For testing. This method can be removed at any time.
+    private static Event createTestEvent(ArrayList<Event> evList, int id,
+            int startMinute, int endMinute) {
+        Event ev = new Event();
+        ev.title = "ev" + id;
+        ev.startDay = 1;
+        ev.endDay = 1;
+        ev.setStartMillis(startMinute);
+        ev.setEndMillis(endMinute);
+        evList.add(ev);
+        return ev;
+    }
+
+    public final void dump() {
+        Log.e("Cal", "+-----------------------------------------+");
+        Log.e("Cal", "+        id = " + id);
+        Log.e("Cal", "+     color = " + color);
+        Log.e("Cal", "+     title = " + title);
+        Log.e("Cal", "+  location = " + location);
+        Log.e("Cal", "+    allDay = " + allDay);
+        Log.e("Cal", "+  startDay = " + startDay);
+        Log.e("Cal", "+    endDay = " + endDay);
+        Log.e("Cal", "+ startTime = " + startTime);
+        Log.e("Cal", "+   endTime = " + endTime);
+    }
+
+    public final boolean intersects(int julianDay, int startMinute,
+            int endMinute) {
+        if (endDay < julianDay) {
+            return false;
+        }
+
+        if (startDay > julianDay) {
+            return false;
+        }
+
+        if (endDay == julianDay) {
+            if (endTime < startMinute) {
+                return false;
+            }
+            // An event that ends at the start minute should not be considered
+            // as intersecting the given time span, but don't exclude
+            // zero-length (or very short) events.
+            if (endTime == startMinute
+                    && (startTime != endTime || startDay != endDay)) {
+                return false;
+            }
+        }
+
+        if (startDay == julianDay && startTime > endMinute) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the event title and location separated by a comma.  If the
+     * location is already part of the title (at the end of the title), then
+     * just the title is returned.
+     *
+     * @return the event title and location as a String
+     */
+    public String getTitleAndLocation() {
+        String text = title.toString();
+
+        // Append the location to the title, unless the title ends with the
+        // location (for example, "meeting in building 42" ends with the
+        // location).
+        if (location != null) {
+            String locationString = location.toString();
+            if (!text.endsWith(locationString)) {
+                text += ", " + locationString;
+            }
+        }
+        return text;
+    }
+
+    public void setColumn(int column) {
+        mColumn = column;
+    }
+
+    public int getColumn() {
+        return mColumn;
+    }
+
+    public void setMaxColumns(int maxColumns) {
+        mMaxColumns = maxColumns;
+    }
+
+    public int getMaxColumns() {
+        return mMaxColumns;
+    }
+
+    public void setStartMillis(long startMillis) {
+        this.startMillis = startMillis;
+    }
+
+    public long getStartMillis() {
+        return startMillis;
+    }
+
+    public void setEndMillis(long endMillis) {
+        this.endMillis = endMillis;
+    }
+
+    public long getEndMillis() {
+        return endMillis;
+    }
+}
diff --git a/src/com/android/calendar/EventGeometry.java b/src/com/android/calendar/EventGeometry.java
new file mode 100644 (file)
index 0000000..ebfa6a3
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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 android.graphics.Rect;
+
+public class EventGeometry {
+    // This is the space from the grid line to the event rectangle.
+    private int mCellMargin = 0;
+
+    private float mMinuteHeight;
+    
+    private float mHourGap;
+    private float mMinEventHeight;
+    
+    void setCellMargin(int cellMargin) {
+        mCellMargin = cellMargin;
+    }
+    
+    void setHourGap(float gap) {
+        mHourGap = gap;
+    }
+    
+    void setMinEventHeight(float height) {
+        mMinEventHeight = height;
+    }
+    
+    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) {
+        if (event.allDay) {
+            return false;
+        }
+
+        float cellMinuteHeight = mMinuteHeight;
+        int startDay = event.startDay;
+        int endDay = event.endDay;
+        
+        if (startDay > date || endDay < date) {
+            return false;
+        }
+        
+        int startTime = event.startTime;
+        int endTime = event.endTime;
+        
+        // If the event started on a previous day, then show it starting
+        // at the beginning of this day.
+        if (startDay < date) {
+            startTime = 0;
+        }
+        
+        // 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;
+        }
+
+        int col = event.getColumn();
+        int maxCols = event.getMaxColumns();
+        int startHour = startTime / 60;
+        int endHour = endTime / 60;
+
+        // If the end point aligns on a cell boundary then count it as
+        // ending in the previous cell so that we don't cross the border
+        // between hours.
+        if (endHour * 60 == endTime)
+            endHour -= 1;
+
+        event.top = top;
+        event.top += (int) (startTime * cellMinuteHeight);
+        event.top += startHour * mHourGap;
+
+        event.bottom = top;
+        event.bottom += (int) (endTime * cellMinuteHeight);
+        event.bottom += endHour * mHourGap;
+
+        // Make the rectangle be at least mMinEventHeight pixels high
+        if (event.bottom < event.top + mMinEventHeight) {
+            event.bottom = event.top + mMinEventHeight;
+        }
+        
+        float colWidth = (float) (cellWidth - 2 * mCellMargin) / (float) maxCols;
+        event.left = left + mCellMargin + col * colWidth;
+        event.right = event.left + colWidth;
+        return true;
+    }
+    
+    // Computes the busy bits.  For each interval containing "interval" minutes,
+    // the busy bit for that interval is set to 1 if the given event overlaps
+    // that interval.
+    void computeBusyBits(int firstDate, int numDays, byte[][] busyBits, Event event, int interval) {
+        if (event.allDay) {
+            return;
+        }
+
+        int endDate = firstDate + numDays;
+        int startDay = event.startDay;
+        int endDay = event.endDay;
+        if (startDay >= endDate || endDay < firstDate) {
+            return;
+        }
+
+        int startTime = event.startTime;
+        
+        int day = startDay;
+        
+        // If the event started on a previous day, then show it starting
+        // at the beginning of this day.
+        if (day < firstDate) {
+            day = firstDate;
+            startTime = 0;
+        }
+        
+        if (endDay >= endDate) {
+            endDay = endDate - 1;
+        }
+        
+        int dayIndex = day - firstDate;
+        while (day <= endDay) {
+            int endTime = event.endTime;
+            // If the event ends on a future day, then show it extending to
+            // the end of this day.
+            if (endDay > day) {
+                endTime = CalendarView.MINUTES_PER_DAY;
+            }
+
+            int startInterval = startTime / interval;
+            int endInterval = (endTime + interval - 1) / interval;
+            
+            for (int ii = startInterval; ii < endInterval; ii++) {
+                busyBits[dayIndex][ii] = 1;
+            }
+            day += 1;
+            dayIndex += 1;
+            startTime = 0;
+        }
+    }
+    
+    /**
+     * Returns true if this event intersects the selection region.
+     */
+    boolean eventIntersectsSelection(Event event, Rect selection) {
+        if (event.left < selection.right && event.right >= selection.left
+                && event.top < selection.bottom && event.bottom >= selection.top) {
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Computes the distance from the given point to the given event.
+     */
+    float pointToEvent(float x, float y, Event event) {
+        float left = event.left;
+        float right = event.right;
+        float top = event.top;
+        float bottom = event.bottom;
+        
+        if (x >= left) {
+            if (x <= right) {
+                if (y >= top) {
+                    if (y <= bottom) {
+                        // x,y is inside the event rectangle
+                        return 0f;
+                    }
+                    // x,y is below the event rectangle
+                    return y - bottom;
+                }
+                // x,y is above the event rectangle
+                return top - y;
+            }
+            
+            // x > right
+            float dx = x - right;
+            if (y < top) {
+                // the upper right corner
+                float dy = top - y;
+                return (float) Math.sqrt(dx * dx + dy * dy);
+            }
+            if (y > bottom) {
+                // the lower right corner
+                float dy = y - bottom;
+                return (float) Math.sqrt(dx * dx + dy * dy);
+            }
+            // x,y is to the right of the event rectangle
+            return dx;
+        }
+        // x < left
+        float dx = left - x;
+        if (y < top) {
+            // the upper left corner
+            float dy = top - y;
+            return (float) Math.sqrt(dx * dx + dy * dy);
+        }
+        if (y > bottom) {
+            // the lower left corner
+            float dy = y - bottom;
+            return (float) Math.sqrt(dx * dx + dy * dy);
+        }
+        // x,y is to the left of the event rectangle
+        return dx;
+    }
+}
diff --git a/src/com/android/calendar/EventInfoActivity.java b/src/com/android/calendar/EventInfoActivity.java
new file mode 100644 (file)
index 0000000..8615c2a
--- /dev/null
@@ -0,0 +1,565 @@
+/*
+ * 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.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.EventRecurrence;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+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.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class EventInfoActivity extends Activity implements View.OnClickListener {
+    private static final int MAX_REMINDERS = 5;
+
+    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
+    };
+    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_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 String[] ATTENDEES_PROJECTION = new String[] {
+        Attendees._ID,                      // 0
+        Attendees.ATTENDEE_RELATIONSHIP,    // 1
+        Attendees.ATTENDEE_STATUS,          // 2
+    };
+    private static final int ATTENDEES_INDEX_RELATIONSHIP = 1;
+    private static final int ATTENDEES_INDEX_STATUS = 2;
+    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
+
+    private static final String[] CALENDARS_PROJECTION = new String[] {
+        Calendars._ID,          // 0
+        Calendars.DISPLAY_NAME, // 1
+    };
+    private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+    private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
+
+    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 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_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 Uri mUri;
+    private long mEventId;
+    private Cursor mEventCursor;
+    private Cursor mAttendeesCursor;
+    private Cursor mCalendarsCursor;
+
+    private long mStartMillis;
+    private long mEndMillis;
+    private int mVisibility = Calendars.NO_ACCESS;
+    private int mRelationship = Attendees.RELATIONSHIP_ORGANIZER;
+
+    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 DeleteEventHelper mDeleteEventHelper;
+
+    private int mResponseOffset;
+
+    // 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();
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // 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);
+        mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
+        initEventCursor();
+
+        setContentView(R.layout.event_info_activity);
+
+        // Attendees cursor
+        Uri uri = Attendees.CONTENT_URI;
+        String where = String.format(ATTENDEES_WHERE, mEventId);
+        mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, null);
+        initAttendeesCursor();
+
+        // Calendars cursor
+        uri = Calendars.CONTENT_URI;
+        where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID));
+        mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null);
+        initCalendarsCursor();
+
+        Resources res = getResources();
+
+        if (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
+                mRelationship == Attendees.RELATIONSHIP_ATTENDEE) {
+            setTitle(res.getString(R.string.event_info_title_invite));
+        } else {
+            setTitle(res.getString(R.string.event_info_title));
+        }
+
+        // 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 = PreferenceManager.getDefaultSharedPreferences(this);
+        String durationString =
+                prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
+        mDefaultReminderMinutes = Integer.parseInt(durationString);
+
+        mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container);
+
+        // 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, 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();
+            }
+        }
+
+        updateView();
+        updateRemindersVisibility();
+
+        mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        initEventCursor();
+        initAttendeesCursor();
+        initCalendarsCursor();
+    }
+
+
+    private void initEventCursor() {
+        if ((mEventCursor != null) && (mEventCursor.getCount() > 0)) {
+            mEventCursor.moveToFirst();
+            mVisibility = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL);
+            mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
+        }
+    }
+
+    private void initAttendeesCursor() {
+        if (mAttendeesCursor != null) {
+            if (mAttendeesCursor.moveToFirst()) {
+                mRelationship = mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP);
+            }
+        }
+    }
+
+    private void initCalendarsCursor() {
+        if (mCalendarsCursor != null) {
+            mCalendarsCursor.moveToFirst();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        ContentResolver cr = getContentResolver();
+        ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems,
+                mReminderValues);
+        EditEvent.saveReminders(cr, mEventId, reminderMinutes, mOriginalMinutes);
+        saveResponse();
+    }
+
+    @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) {
+        // Cannot add reminders to a shared calendar with only free/busy
+        // permissions
+        if (mVisibility >= Calendars.READ_ACCESS && 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 (mVisibility >= Calendars.CONTRIBUTOR_ACCESS &&
+                mRelationship >= Attendees.RELATIONSHIP_ORGANIZER) {
+            menu.setGroupVisible(MENU_GROUP_EDIT, true);
+            menu.setGroupEnabled(MENU_GROUP_EDIT, true);
+            menu.setGroupVisible(MENU_GROUP_DELETE, true);
+            menu.setGroupEnabled(MENU_GROUP_DELETE, true);
+        } else {
+            menu.setGroupVisible(MENU_GROUP_EDIT, false);
+            menu.setGroupEnabled(MENU_GROUP_EDIT, false);
+            menu.setGroupVisible(MENU_GROUP_DELETE, false);
+            menu.setGroupEnabled(MENU_GROUP_DELETE, false);
+        }
+
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (item.getItemId()) {
+        case MENU_ADD_REMINDER:
+            // 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();
+            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 (mReminderItems.size() == 0) {
+            mRemindersContainer.setVisibility(View.GONE);
+        } else {
+            mRemindersContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void saveResponse() {
+        if (mAttendeesCursor == null) {
+            return;
+        }
+        Spinner spinner = (Spinner) findViewById(R.id.response_value);
+        int position = spinner.getSelectedItemPosition() - mResponseOffset;
+        if (position <= 0) {
+            return;
+        }
+
+        int status = ATTENDEE_VALUES[position];
+        mAttendeesCursor.updateInt(ATTENDEES_INDEX_STATUS, status);
+        mAttendeesCursor.commitUpdates();
+    }
+
+    private int findResponseIndexFor(int response) {
+        int size = ATTENDEE_VALUES.length;
+        for (int index = 0; index < size; index++) {
+            if (ATTENDEE_VALUES[index] == response) {
+                return index;
+            }
+        }
+        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();
+    }
+
+    private void doDelete() {
+        mDeleteEventHelper.delete(mStartMillis, mEndMillis, mEventCursor, -1);
+    }
+
+    private void updateView() {
+        if (mEventCursor == null) {
+            return;
+        }
+        Resources res = getResources();
+        ContentResolver cr = getContentResolver();
+
+        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
+        if (eventName == null || eventName.length() == 0) {
+            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);
+        int color = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
+
+        ImageView stripe = (ImageView) findViewById(R.id.vertical_stripe);
+        stripe.getBackground().setColorFilter(color, 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 = DateUtils.formatDateRange(mStartMillis, mEndMillis, flags);
+        setTextCommon(R.id.when, when);
+
+        // 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) {
+            setTextCommon(R.id.timezone, localTimezone);
+        } else {
+            setVisibilityCommon(R.id.timezone_container, View.GONE);
+        }
+
+        // Repeat
+        if (rRule != null) {
+            EventRecurrence eventRecurrence = new EventRecurrence();
+            eventRecurrence.parse(rRule);
+            Time date = new Time();
+            if (allDay) {
+                date.timezone = Time.TIMEZONE_UTC;
+            }
+            date.set(mStartMillis);
+            eventRecurrence.setStartDate(date);
+            String repeatString = eventRecurrence.getRepeatString();
+            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 {
+            setTextCommon(R.id.where, location);
+        }
+
+        // Description
+        if (description == null || description.length() == 0) {
+            setVisibilityCommon(R.id.description, View.GONE);
+        } else {
+            setTextCommon(R.id.description, description);
+        }
+
+        // Calendar
+        if (mCalendarsCursor != null) {
+            mCalendarsCursor.moveToFirst();
+            String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+            setTextCommon(R.id.calendar, calendarName);
+        } else {
+            setVisibilityCommon(R.id.calendar_container, View.GONE);
+        }
+
+        // Response
+        updateResponse();
+    }
+
+    void updateResponse() {
+        if (mVisibility < Calendars.CONTRIBUTOR_ACCESS ||
+                mRelationship != Attendees.RELATIONSHIP_ATTENDEE) {
+            setVisibilityCommon(R.id.response_container, View.GONE);
+            return;
+        }
+
+        setVisibilityCommon(R.id.response_container, View.VISIBLE);
+
+        Spinner spinner = (Spinner) findViewById(R.id.response_value);
+
+        int response = ATTENDEE_NO_RESPONSE;
+        if (mAttendeesCursor != null) {
+            response = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
+        }
+        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 ((response != Attendees.ATTENDEE_STATUS_INVITED)
+                && (response != ATTENDEE_NO_RESPONSE)
+                && (response != 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 = findResponseIndexFor(response);
+        spinner.setSelection(index + mResponseOffset);
+    }
+
+    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;
+    }
+}
diff --git a/src/com/android/calendar/EventLoader.java b/src/com/android/calendar/EventLoader.java
new file mode 100644 (file)
index 0000000..eb8a82d
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * 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 android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Handler;
+import android.os.Process;
+import android.provider.Calendar.BusyBits;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class EventLoader {
+    
+    private Context mContext;
+    private Handler mHandler = new Handler();
+    private AtomicInteger mSequenceNumber = new AtomicInteger();
+    
+    private LinkedBlockingQueue<LoadRequest> mLoaderQueue;
+    private LoaderThread mLoaderThread;
+    private ContentResolver mResolver;
+    
+    private static interface LoadRequest {
+        public void processRequest(EventLoader eventLoader);
+        public void skipRequest(EventLoader eventLoader);
+    }
+    
+    private static class ShutdownRequest implements LoadRequest {
+        public void processRequest(EventLoader eventLoader) {
+        }
+
+        public void skipRequest(EventLoader eventLoader) {
+        }
+    }
+    
+    private static class LoadBusyBitsRequest implements LoadRequest {
+        public int startDay;
+        public int numDays;
+        public int[] busybits;
+        public int[] allDayCounts;
+        public Runnable uiCallback;
+        
+        public LoadBusyBitsRequest(int startDay, int numDays, int[] busybits, int[] allDayCounts,
+                final Runnable uiCallback) {
+            this.startDay = startDay;
+            this.numDays = numDays;
+            this.busybits = busybits;
+            this.allDayCounts = allDayCounts;
+            this.uiCallback = uiCallback;
+        }
+        
+        public void processRequest(EventLoader eventLoader) {
+            final Handler handler = eventLoader.mHandler;
+            ContentResolver cr = eventLoader.mResolver;
+            
+            // Clear the busy bits and all-day counts
+            for (int dayIndex = 0; dayIndex < numDays; dayIndex++) {
+                busybits[dayIndex] = 0;
+                allDayCounts[dayIndex] = 0;
+            }
+
+            Cursor cursor = BusyBits.query(cr, startDay, numDays);
+            try {
+                int dayColumnIndex = cursor.getColumnIndexOrThrow(BusyBits.DAY);
+                int busybitColumnIndex = cursor.getColumnIndexOrThrow(BusyBits.BUSYBITS);
+                int allDayCountColumnIndex = cursor.getColumnIndexOrThrow(BusyBits.ALL_DAY_COUNT);
+                
+                while (cursor.moveToNext()) {
+                    int day = cursor.getInt(dayColumnIndex);
+                    int dayIndex = day - startDay;
+                    busybits[dayIndex] = cursor.getInt(busybitColumnIndex);
+                    allDayCounts[dayIndex] = cursor.getInt(allDayCountColumnIndex);
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            handler.post(uiCallback);
+        }
+
+        public void skipRequest(EventLoader eventLoader) {
+        }
+    }
+    
+    private static class LoadEventsRequest implements LoadRequest {
+        
+        public int id;
+        public long startMillis;
+        public int numDays;
+        public ArrayList<Event> events;
+        public Runnable successCallback;
+        public Runnable cancelCallback;
+
+        public LoadEventsRequest(int id, long startMillis, int numDays, ArrayList<Event> events,
+                final Runnable successCallback, final Runnable cancelCallback) {
+            this.id = id;
+            this.startMillis = startMillis;
+            this.numDays = numDays;
+            this.events = events;
+            this.successCallback = successCallback;
+            this.cancelCallback = cancelCallback;
+        }
+        
+        public void processRequest(EventLoader eventLoader) {
+            Event.loadEvents(eventLoader.mContext, events, startMillis,
+                    numDays, id, eventLoader.mSequenceNumber);
+            
+            // Check if we are still the most recent request.
+            if (id == eventLoader.mSequenceNumber.get()) {
+                eventLoader.mHandler.post(successCallback);
+            } else {
+                eventLoader.mHandler.post(cancelCallback);
+            }
+        }
+
+        public void skipRequest(EventLoader eventLoader) {
+            eventLoader.mHandler.post(cancelCallback);
+        }
+    }
+    
+    private static class LoaderThread extends Thread {
+        LinkedBlockingQueue<LoadRequest> mQueue;
+        EventLoader mEventLoader;
+        
+        public LoaderThread(LinkedBlockingQueue<LoadRequest> queue, EventLoader eventLoader) {
+            mQueue = queue;
+            mEventLoader = eventLoader;
+        }
+        
+        public void shutdown() {
+            try {
+                mQueue.put(new ShutdownRequest());
+            } catch (InterruptedException ex) {
+                // The put() method fails with InterruptedException if the
+                // queue is full. This should never happen because the queue
+                // has no limit.
+                Log.e("Cal", "LoaderThread.shutdown() interrupted!");
+            }
+        }
+        
+        @Override
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            while (true) {
+                try {
+                    // Wait for the next request
+                    LoadRequest request = mQueue.take();
+                    
+                    // If there are a bunch of requests already waiting, then
+                    // skip all but the most recent request.
+                    while (!mQueue.isEmpty()) {
+                        // Let the request know that it was skipped
+                        request.skipRequest(mEventLoader);
+                        
+                        // Skip to the next request
+                        request = mQueue.take();
+                    }
+
+                    if (request instanceof ShutdownRequest) {
+                        return;
+                    }
+                    request.processRequest(mEventLoader);
+                } catch (InterruptedException ex) {
+                    Log.e("Cal", "background LoaderThread interrupted!");
+                }
+            }
+        }
+    }
+    
+    public EventLoader(Context context) {
+        mContext = context;
+        mLoaderQueue = new LinkedBlockingQueue<LoadRequest>();
+        mResolver = context.getContentResolver();
+    }
+    
+    /**
+     * Call this from the activity's onResume()
+     */
+    public void startBackgroundThread() {
+        mLoaderThread = new LoaderThread(mLoaderQueue, this);
+        mLoaderThread.start();
+    }
+    
+    /**
+     * Call this from the activity's onPause()
+     */
+    public void stopBackgroundThread() {
+        mLoaderThread.shutdown();
+    }
+
+    /**
+     * Loads "numDays" days worth of events, starting at start, into events.
+     * Posts uiCallback to the {@link Handler} for this view, which will run in the UI thread.
+     * Reuses an existing background thread, if events were already being loaded in the background.
+     * NOTE: events and uiCallback are not used if an existing background thread gets reused --
+     * the ones that were passed in on the call that results in the background thread getting
+     * 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,
+            long start, final Runnable successCallback, final Runnable cancelCallback) {
+        
+        // Increment the sequence number for requests.  We don't care if the
+        // sequence numbers wrap around because we test for equality with the
+        // latest one.
+        int id = mSequenceNumber.incrementAndGet();
+
+        // Send the load request to the background thread
+        LoadEventsRequest request = new LoadEventsRequest(id, start, numDays,
+                events, successCallback, cancelCallback);
+
+        try {
+            mLoaderQueue.put(request);
+        } catch (InterruptedException ex) {
+            // The put() method fails with InterruptedException if the
+            // queue is full. This should never happen because the queue
+            // has no limit.
+            Log.e("Cal", "loadEventsInBackground() interrupted!");
+        }
+    }
+    
+    void loadBusyBitsInBackground(int startDay, int numDays, int[] busybits, int[] allDayCounts,
+            final Runnable uiCallback) {
+        // Send the load request to the background thread
+        LoadBusyBitsRequest request = new LoadBusyBitsRequest(startDay, numDays, busybits,
+                allDayCounts, uiCallback);
+
+        try {
+            mLoaderQueue.put(request);
+        } catch (InterruptedException ex) {
+            // The put() method fails with InterruptedException if the
+            // queue is full. This should never happen because the queue
+            // has no limit.
+            Log.e("Cal", "loadBusyBitsInBackground() interrupted!");
+        }
+    }
+}
diff --git a/src/com/android/calendar/IcsImportActivity.java b/src/com/android/calendar/IcsImportActivity.java
new file mode 100644 (file)
index 0000000..c4f5fde
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.ICalendar;
+import android.provider.Calendar;
+import android.util.Config;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+public class IcsImportActivity extends Activity {
+
+    private static final String TAG = "Calendar";
+
+    // TODO: consolidate this code with the EventActivity
+    private static class CalendarInfo {
+        public final long id;
+        public final String name;
+
+        public CalendarInfo(long id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    private View mView;
+    private Button mImportButton;
+    private Button mCancelButton;
+    private Spinner mCalendars;
+    private ImageView mCalendarIcon;
+    private TextView mNumEvents;
+
+    private ICalendar.Component mCalendar = null;
+
+    private View.OnClickListener mImportListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            importCalendar();
+            finish();
+        }
+    };
+
+    private View.OnClickListener mCancelListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            finish();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.ics_import_activity);
+        mView = findViewById(R.id.import_ics);
+
+        mCalendarIcon = (ImageView) findViewById(R.id.calendar_icon);
+        mCalendars = (Spinner) findViewById(R.id.calendars);
+        populateCalendars();
+
+        mImportButton = (Button) findViewById(R.id.import_button);
+        mImportButton.setOnClickListener(mImportListener);
+        mCancelButton = (Button) findViewById(R.id.cancel_button);
+        mCancelButton.setOnClickListener(mCancelListener);
+
+        mNumEvents = (TextView) findViewById(R.id.num_events);
+
+        Intent intent = getIntent();
+        String data = intent.getStringExtra("ics");
+        if (data == null) {
+            Uri content = intent.getData();
+            if (content != null) {
+                InputStream is = null;
+                try {
+                    is = getContentResolver().openInputStream(content);
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    byte[] buf = new byte[8096];
+                    int bytesRead = -1;
+                    int pos = 0;
+                    while ((bytesRead = is.read(buf)) != -1) {
+                        baos.write(buf, pos, bytesRead);
+                        pos += bytesRead;
+                    }
+                    data = new String(baos.toByteArray(), "UTF-8");
+                } catch (FileNotFoundException fnfe) {
+                    Log.w(TAG, "Could not open data.", fnfe);
+                } catch (IOException ioe) {
+                    Log.w(TAG, "Could not read data.", ioe);
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException ioe) {
+                            Log.w(TAG, "Could not close InputStream.", ioe);
+                        }
+                    }
+                }
+            }
+        }
+        if (data == null) {
+            Log.w(TAG, "No iCalendar data to parse.");
+            finish();
+            return;
+        }
+        parseCalendar(data);
+    }
+
+    private void populateCalendars() {
+        ContentResolver cr = getContentResolver();
+        Cursor c = cr.query(Calendar.Calendars.CONTENT_URI,
+                      new String[] { Calendar.Calendars._ID,
+                                     Calendar.Calendars.DISPLAY_NAME,
+                                     Calendar.Calendars.SELECTED,
+                                     Calendar.Calendars.ACCESS_LEVEL },
+                      Calendar.Calendars.SELECTED + "=1 AND "
+                          + Calendar.Calendars.ACCESS_LEVEL + ">="
+                          + Calendar.Calendars.CONTRIBUTOR_ACCESS,
+                      null, null /* sort order */);
+
+        ArrayList<CalendarInfo> items = new ArrayList<CalendarInfo>();
+        try {
+            // TODO: write a custom adapter that wraps the cursor?
+            int idColumn = c.getColumnIndex(Calendar.Calendars._ID);
+            int nameColumn = c.getColumnIndex(Calendar.Calendars.DISPLAY_NAME);
+            while (c.moveToNext()) {
+                long id = c.getLong(idColumn);
+                String name = c.getString(nameColumn);
+                items.add(new CalendarInfo(id, name));
+            }
+        } finally {
+            c.deactivate();
+        }
+
+        mCalendars.setAdapter(new ArrayAdapter<CalendarInfo>(this,
+                android.R.layout.simple_spinner_item, items));
+    }
+
+    private void parseCalendar(String data) {
+        mCalendar = null;
+        try {
+            mCalendar = ICalendar.parseCalendar(data);
+        } catch (ICalendar.FormatException fe) {
+            if (Config.LOGD) {
+                Log.d(TAG, "Could not parse iCalendar.", fe);
+                // TODO: show an error message.
+                finish();
+                return;
+            }
+        }
+        if (mCalendar.getComponents() == null) {
+            Log.d(TAG, "No events in iCalendar.");
+            finish();
+            return;
+        }
+        int numEvents = 0;
+        for (ICalendar.Component component : mCalendar.getComponents()) {
+            if ("VEVENT".equals(component.getName())) {
+                // TODO: display a list of the events (start time, title) in
+                // the UI?
+                ++numEvents;
+            }
+        }
+        // TODO: special-case a single-event calendar.  switch to the
+        // EventActivity, once the EventActivity supports displaying data that
+        // is passed in via the extras.
+        // OR, we could flip things around, where the EventActivity handles ICS
+        // import by default, and delegates to the IcsImportActivity if it finds
+        // that there are more than one event in the iCalendar.  that would
+        // avoid an extra activity launch for the expected common case of
+        // importing a single event.
+        mNumEvents.setText(Integer.toString(numEvents));
+    }
+
+    private void importCalendar() {
+
+        ContentResolver cr = getContentResolver();
+
+        int numImported = 0;
+        ContentValues values = new ContentValues();
+
+        for (ICalendar.Component component : mCalendar.getComponents()) {
+            if ("VEVENT".equals(component.getName())) {
+                CalendarInfo calInfo =
+                        (CalendarInfo) mCalendars.getSelectedItem();
+                if (Calendar.Events.insertVEvent(cr, component, calInfo.id,
+                        Calendar.Events.STATUS_CONFIRMED, values) != null) {
+                    ++numImported;
+                }
+            }
+        }
+        // TODO: display how many were imported.
+    }
+}
diff --git a/src/com/android/calendar/LaunchActivity.java b/src/com/android/calendar/LaunchActivity.java
new file mode 100644 (file)
index 0000000..d688059
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Calendar.Calendars;
+
+public class LaunchActivity extends Activity implements OnCancelListener,
+        OnClickListener, Runnable {
+
+    private static final String[] PROJECTION = new String[] {
+        Calendars._ID,
+    };
+    
+    public void run() {
+        /* Start a query to refresh the list of calendars if for some reason
+         * the list was not fetched from the server.  We don't care about
+         * the contents of the returned cursor; we do the query strictly for
+         * the side-effect of refreshing the list of calendars from the server.
+         */
+        final ContentResolver cr = getContentResolver();
+        Cursor cursor = cr.query(Calendars.LIVE_CONTENT_URI, PROJECTION,
+            null, null, null);
+        
+        if (cursor != null) {
+            cursor.close();
+        }
+    }
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        // Check to see if there are no calendars
+        final ContentResolver cr = getContentResolver();
+        Cursor cursor = cr.query(Calendars.CONTENT_URI, PROJECTION,
+                null /* selection */,
+                null /* selectionArgs */,
+                Calendars.DEFAULT_SORT_ORDER);
+        
+        boolean missingCalendars = false;
+        if ((cursor == null) || (cursor.getCount() == 0)) {
+            missingCalendars = true;
+        }
+        
+        if (cursor != null) {
+            cursor.close();
+        }
+        
+        if (missingCalendars) {
+            new AlertDialog.Builder(this)
+                    .setTitle(R.string.no_calendars)
+                    .setMessage(R.string.no_calendars_msg)
+                    .setCancelable(true)
+                    .setOnCancelListener(this)
+                    .setPositiveButton(R.string.ok_label, this)
+                    .show();
+            new Thread(this).start();
+            return;
+        }
+            
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        String startActivity = prefs.getString(CalendarPreferenceActivity.KEY_START_VIEW,
+                CalendarPreferenceActivity.DEFAULT_START_VIEW);
+            
+        // 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);
+        }
+        intent.setClassName(this, startActivity);
+        startActivity(intent);
+        finish();
+    }
+
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        finish();
+    }
+}
diff --git a/src/com/android/calendar/MenuHelper.java b/src/com/android/calendar/MenuHelper.java
new file mode 100644 (file)
index 0000000..5ee0be4
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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.pim.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, false);
+            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, false);
+            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, false);
+            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, false);
+            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_AGENDA, MENU_AGENDA, 0, R.string.agenda_view);
+        item.setIcon(android.R.drawable.ic_menu_agenda);
+        item.setAlphabeticShortcut('a');
+        
+        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_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_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_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:
+            switchTo(activity, CalendarPreferenceActivity.class.getName(), nav.getSelectedTime());
+            return true;
+        case MENU_AGENDA:
+            switchTo(activity, AgendaActivity.class.getName(), nav.getSelectedTime());
+            activity.finish();
+            return true;
+        case MENU_DAY:
+            switchTo(activity, DayActivity.class.getName(), nav.getSelectedTime());
+            activity.finish();
+            return true;
+        case MENU_WEEK:
+            switchTo(activity, WeekActivity.class.getName(), nav.getSelectedTime());
+            activity.finish();
+            return true;
+        case MENU_MONTH:
+            switchTo(activity, MonthActivity.class.getName(), nav.getSelectedTime());
+            activity.finish();
+            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;
+    }
+    
+    /* package */ static void switchTo(Activity activity, String className, long startMillis) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClassName(activity, className);
+        intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+        activity.startActivity(intent);
+    }
+}
diff --git a/src/com/android/calendar/MonthActivity.java b/src/com/android/calendar/MonthActivity.java
new file mode 100644 (file)
index 0000000..11fdf5b
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * 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 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.pim.DateUtils;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+import android.provider.Calendar.Events;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
+import android.widget.Gallery.LayoutParams;
+
+import java.util.Calendar;
+
+public class MonthActivity extends Activity implements ViewSwitcher.ViewFactory,
+        Navigator, AnimationListener {
+    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;
+
+    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.FILL_PARENT, LayoutParams.FILL_PARENT));
+        mv.setSelectedTime(mTime);
+        return mv;
+    }
+
+    /* Navigator interface methods */
+    public void goTo(Time time) {
+        TextView title = (TextView) findViewById(R.id.title);
+        title.setText(Utils.formatMonthYear(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().
+        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();
+        now.set(System.currentTimeMillis());
+
+        TextView title = (TextView) findViewById(R.id.title);
+        title.setText(Utils.formatMonthYear(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();
+    }
+
+    /**
+     * 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();
+        }
+    };
+
+    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) {
+    }
+
+    @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
+        long oldHeapSize = VMRuntime.getRuntime().setMinimumHeapSize(INITIAL_HEAP_SIZE);
+
+        setContentView(R.layout.month_activity);
+        mContentResolver = getContentResolver();
+
+        long time;
+        if (icicle != null) {
+            time = icicle.getLong(EVENT_BEGIN_TIME);
+        } else {
+            time = Utils.timeFromIntentInMillis(getIntent());
+        }
+
+        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;
+
+        String dayString = DateUtils.getDayOfWeekString((Calendar.SUNDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day0)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.MONDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day1)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.TUESDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day2)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.WEDNESDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day3)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.THURSDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day4)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.FRIDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day5)).setText(dayString);
+        dayString = DateUtils.getDayOfWeekString((Calendar.SATURDAY + diff) % 7 + 1,
+                DateUtils.LENGTH_MEDIUM);
+        ((TextView) findViewById(R.id.day6)).setText(dayString);
+
+        // Set the initial title
+        TextView title = (TextView) findViewById(R.id.title);
+        title.setText(Utils.formatMonthYear(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 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();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mEventLoader.startBackgroundThread();
+        eventsChanged();
+
+        MonthView view1 = (MonthView) mSwitcher.getCurrentView();
+        MonthView view2 = (MonthView) mSwitcher.getNextView();
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        String str = prefs.getString(CalendarPreferenceActivity.KEY_DETAILED_VIEW,
+                CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW);
+        view1.setDetailedView(str);
+        view2.setDetailedView(str);
+
+        // Record Month View as the (new) start view
+        String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.MONTH_VIEW_ID];
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
+        editor.commit();
+
+        // 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
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putLong(EVENT_BEGIN_TIME, mTime.toMillis(true));
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                finish();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @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);
+    }
+}
diff --git a/src/com/android/calendar/MonthView.java b/src/com/android/calendar/MonthView.java
new file mode 100644 (file)
index 0000000..5949d95
--- /dev/null
@@ -0,0 +1,1351 @@
+/*
+ * 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.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.provider.Calendar.BusyBits;
+import android.util.DayOfMonthCursor;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+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.ContextMenu.ContextMenuInfo;
+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 final boolean DEBUG_BUSYBITS = false;
+
+    private static final int WEEK_GAP = 1;
+    private static final int MONTH_DAY_GAP = 1;
+    private static final float HOUR_GAP = 0.5f;
+
+    private static final int MONTH_DAY_TEXT_SIZE = 20;
+    private static final int WEEK_BANNER_HEIGHT = 17;
+    private static final int WEEK_TEXT_SIZE = 15;
+    private static final int WEEK_TEXT_PADDING = 3;
+    private static final int BUSYBIT_WIDTH = 10;
+    private static final int BUSYBIT_RIGHT_MARGIN = 3;
+    private static final int BUSYBIT_TOP_BOTTOM_MARGIN = 7;
+
+    private static final 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 = new Time();   // the time when we entered this view
+
+    // This Time object is used to set the time for the other Month view.
+    private Time mOtherViewCalendar = new Time();
+
+    // This Time object is used for temporary calculations and is allocated
+    // once to avoid extra garbage collection
+    private Time mTempTime = new Time();
+
+    private DayOfMonthCursor mCursor;
+
+    private Drawable mBoxSelected;
+    private Drawable mBoxPressed;
+    private Drawable mBoxLongPressed;
+    private Drawable mDnaEmpty;
+    private Drawable mDnaTop;
+    private Drawable mDnaMiddle;
+    private Drawable mDnaBottom;
+    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();
+
+    // The number of hours represented by one busy bit
+    private static final int HOURS_PER_BUSY_SLOT = 4;
+
+    // The number of database intervals represented by one busy bit (slot)
+    private static final int INTERVALS_PER_BUSY_SLOT = 4 * 60 / BusyBits.MINUTES_PER_BUSY_INTERVAL;
+
+    // The bit mask for coalescing the raw busy bits from the database
+    // (1 bit per hour) into the busy bits per slot (4-hour slots).
+    private static final int BUSY_SLOT_MASK = (1 << INTERVALS_PER_BUSY_SLOT) - 1;
+
+    // The number of slots in a day
+    private static final int SLOTS_PER_DAY = 24 / HOURS_PER_BUSY_SLOT;
+
+    // There is one "busy" bit for each slot of time.
+    private byte[][] mBusyBits = new byte[31][SLOTS_PER_DAY];
+
+    // Raw busy bits from database
+    private int[] mRawBusyBits = new int[31];
+    private int[] mAllDayCounts = new int[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 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 events and day backgrounds.
+    // mEventBitmapCache is indexed by an integer constructed from the bits in the busyBits
+    // field. It is not expected to be larger than 12 bits (if so, we should switch to using a Map).
+    // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
+    private SparseArray<Bitmap> mEventBitmapCache = new SparseArray<Bitmap>(1<<SLOTS_PER_DAY);
+    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;
+
+    // Modulo used to pack (width,height) into a unique integer
+    private static final int MODULO_SHIFT = 16;
+
+    private int mSelectionMode = SELECTION_HIDDEN;
+
+    /**
+     * The first Julian day of the current month.
+     */
+    private int mFirstJulianDay;
+
+    private final EventLoader mEventLoader;
+
+    private ArrayList<Event> mEvents = new ArrayList<Event>();
+
+    private Drawable mTodayBackground;
+    private Drawable mDayBackground;
+
+    // Cached colors
+    private int mMonthOtherMonthColor;
+    private int mMonthWeekBannerColor;
+    private int mMonthOtherMonthBannerColor;
+    private int mMonthOtherMonthDayNumberColor;
+    private int mMonthDayNumberColor;
+    private int mMonthTodayNumberColor;
+
+    public MonthView(MonthActivity activity, Navigator navigator) {
+        super(activity);
+        mEventLoader = activity.mEventLoader;
+        mNavigator = navigator;
+        mEventGeometry = new EventGeometry();
+        mEventGeometry.setMinEventHeight(1.0f);
+        mEventGeometry.setHourGap(HOUR_GAP);
+        init(activity);
+    }
+
+    private void init(MonthActivity activity) {
+        setFocusable(true);
+        setClickable(true);
+        setOnCreateContextMenuListener(this);
+        mParentActivity = activity;
+        mViewCalendar = new Time();
+        long now = System.currentTimeMillis();
+        mViewCalendar.set(now);
+        mViewCalendar.monthDay = 1;
+        long millis = mViewCalendar.normalize(true /* ignore DST */);
+        mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
+        mViewCalendar.set(now);
+
+        mCursor = new DayOfMonthCursor(mViewCalendar.year,  mViewCalendar.month,
+                mViewCalendar.monthDay, mParentActivity.getStartDay());
+        mToday = new Time();
+        mToday.set(System.currentTimeMillis());
+
+        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);
+
+        mDnaEmpty = mResources.getDrawable(R.drawable.dna_empty);
+        mDnaTop = mResources.getDrawable(R.drawable.dna_1_of_6);
+        mDnaMiddle = mResources.getDrawable(R.drawable.dna_2345_of_6);
+        mDnaBottom = mResources.getDrawable(R.drawable.dna_6_of_6);
+        mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
+        mDayBackground = mResources.getDrawable(R.drawable.month_view_background);
+
+        // 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);
+
+        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(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);
+
+                return true;
+            }
+
+            @Override
+            public boolean onDown(MotionEvent e) {
+                mLaunchDayView = false;
+                return true;
+            }
+
+            @Override
+            public void onShowPress(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;
+                }
+
+                // Launch the Day/Agenda view when the finger lifts up,
+                // unless the finger moves before lifting up.
+                mLaunchDayView = true;
+
+                // Highlight the selected day.
+                mCursor.setSelectedRowColumn(row, col);
+                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) {
+                    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);
+                    mParentActivity.finish();
+                }
+
+                return true;
+            }
+        });
+    }
+
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+        MenuItem item;
+
+        item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.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.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();
+                    MenuHelper.switchTo(mParentActivity, DayActivity.class.getName(), startMillis);
+                    mParentActivity.finish();
+                    break;
+                }
+                case MenuHelper.MENU_AGENDA: {
+                    long startMillis = getSelectedTimeInMillis();
+                    MenuHelper.switchTo(mParentActivity, AgendaActivity.class.getName(), startMillis);
+                    mParentActivity.finish();
+                    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(mContext, 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 busy-bits 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;
+        }
+        mEventLoader.loadBusyBitsInBackground(startDay, 31, mRawBusyBits, mAllDayCounts,
+                new Runnable() {
+            public void run() {
+                convertBusyBits();
+                if (PROFILE_LOAD_TIME) {
+                    long endMillis = SystemClock.uptimeMillis();
+                    long elapsed = endMillis - startMillis;
+                    Log.i("Cal", (mViewCalendar.month+1) + "/" + mViewCalendar.year + " Month view load busybits: " + elapsed);
+                }
+                mRedrawScreen = true;
+                mParentActivity.stopProgressSpinner();
+                invalidate();
+            }
+        });
+    }
+
+    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);
+        }
+    }
+
+    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);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureDetector.onTouchEvent(event)) {
+            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);
+        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(mEventBitmapCache);
+        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 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) {
+            boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
+
+            // Adjust cell boundaries to compensate for the different border
+            // style.
+            r.top--;
+            if (column != 0) {
+                r.left--;
+            }
+
+            // Draw cell border
+            p.setColor(mMonthOtherMonthColor);
+            p.setAntiAlias(false);
+
+            if (row == 0) {
+                // Bottom line
+                canvas.drawLine(r.left, r.bottom, r.right, r.bottom, p);
+            }
+
+            // Top line
+            canvas.drawLine(r.left, r.top, r.right, r.top, p);
+
+            // Right line
+            canvas.drawLine(r.right, r.top, r.right, r.bottom, p);
+
+            if (firstDayOfNextmonth && column != 0) {
+                canvas.drawLine(r.left, r.top, r.left, r.bottom, p);
+            }
+        } else if (drawSelection) {
+            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);
+            }
+
+            drawEvents(day, canvas, r, p);
+            if (!mAnimating) {
+                updateEventDetails(day);
+            }
+        } else {
+            // Today gets a different background
+            if (isToday) {
+                // We could cache this for a little bit more performance, but it's not on the
+                // performance radar...
+                Drawable background = mTodayBackground;
+                background.setBounds(r);
+                background.draw(canvas);
+            } else {
+                // Use the bitmap cache to draw the day background
+                int width = r.right - r.left;
+                int height = r.bottom - r.top;
+                // Compute a unique id that depends on width and height.
+                int id = (height << MODULO_SHIFT) | width;
+                Bitmap bitmap = mDayBitmapCache.get(id);
+                if (bitmap == null) {
+                     bitmap = createBitmap(mDayBackground, width, height);
+                     mDayBitmapCache.put(id, bitmap);
+                }
+                canvas.drawBitmap(bitmap, r.left, r.top, p);
+            }
+            drawEvents(day, canvas, r, p);
+        }
+
+        // Draw week number
+        if (mShowWeekNumbers && column == 0) {
+            // Draw the banner
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(mMonthWeekBannerColor);
+            int right = r.right;
+            r.right = right - BUSYBIT_WIDTH - BUSYBIT_RIGHT_MARGIN;
+            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--;
+            }
+            r.right = right;
+
+            // 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 (drawSelection || !isToday) {
+            p.setColor(mMonthDayNumberColor);
+        } else {
+            p.setColor(mMonthTodayNumberColor);
+        }
+
+        p.setTextAlign(Paint.Align.CENTER);
+        int right = r.right - BUSYBIT_WIDTH - BUSYBIT_RIGHT_MARGIN;
+        int textX = r.left + (right - r.left) / 2; // center of text
+        int textY = r.bottom - BUSYBIT_TOP_BOTTOM_MARGIN - 2; // bottom of text
+        canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p);
+    }
+
+    /**
+     * Converts the busy bits from the database that use 1-hour intervals to
+     * the 4-hour time slots needed in this view.  Also, we map all-day
+     * events to the first two 4-hour time slots (that is, an all-day event
+     * will look like the first 8 hours from 12am to 8am are busy).  This
+     * looks better than setting just the first 4-hour time slot because that
+     * is barely visible in landscape mode.
+     */
+    private void convertBusyBits() {
+        if (DEBUG_BUSYBITS) {
+            Log.i("Cal", "convertBusyBits() SLOTS_PER_DAY: " + SLOTS_PER_DAY
+                    + " BUSY_SLOT_MASK: " + BUSY_SLOT_MASK
+                    + " INTERVALS_PER_BUSY_SLOT: " + INTERVALS_PER_BUSY_SLOT);
+            for (int day = 0; day < 31; day++) {
+                int bits = mRawBusyBits[day];
+                String bitString = String.format("0x%06x", bits);
+                String valString = "";
+                for (int slot = 0; slot < SLOTS_PER_DAY; slot++) {
+                    int val = bits & BUSY_SLOT_MASK;
+                    bits = bits >>> INTERVALS_PER_BUSY_SLOT;
+                    valString += " " + val;
+                }
+                Log.i("Cal", "[" + day + "] " + bitString + " " + valString
+                        + " allday: " + mAllDayCounts[day]);
+            }
+        }
+        for (int day = 0; day < 31; day++) {
+            int bits = mRawBusyBits[day];
+            for (int slot = 0; slot < SLOTS_PER_DAY; slot++) {
+                int val = bits & BUSY_SLOT_MASK;
+                bits = bits >>> INTERVALS_PER_BUSY_SLOT;
+                if (val == 0) {
+                    mBusyBits[day][slot] = 0;
+                } else {
+                    mBusyBits[day][slot] = 1;
+                }
+            }
+            if (mAllDayCounts[day] > 0) {
+                mBusyBits[day][0] = 1;
+                mBusyBits[day][1] = 1;
+            }
+        }
+    }
+
+    /**
+     * Create a bitmap at the origin for the given set of busyBits.
+     *
+     * @param busyBits an array of bits with elements set to 1 if we have an event for that slot
+     * @param rect the size of the resulting
+     * @return a new bitmap
+     */
+    private Bitmap createEventBitmap(byte[] busyBits, Rect rect) {
+        // Compute the size of the smallest bitmap, excluding margins.
+        final int left = 0;
+        final int right = BUSYBIT_WIDTH;
+        final int top = 0;
+        final int bottom = (rect.bottom - rect.top) - 2 * BUSYBIT_TOP_BOTTOM_MARGIN;
+        final int height = bottom - top;
+        final int width = right - left;
+
+        final Drawable dnaEmpty = mDnaEmpty;
+        final Drawable dnaTop = mDnaTop;
+        final Drawable dnaMiddle = mDnaMiddle;
+        final Drawable dnaBottom = mDnaBottom;
+        final float slotHeight = (float) height / SLOTS_PER_DAY;
+
+        // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
+        Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
+
+        // Create a canvas for drawing and draw background (dnaEmpty)
+        Canvas canvas = new Canvas(bitmap);
+        dnaEmpty.setBounds(left, top, right, bottom);
+        dnaEmpty.draw(canvas);
+
+        // The first busy bit is a drawable that is round at the top
+        if (busyBits[0] == 1) {
+            float rectBottom = top + slotHeight;
+            dnaTop.setBounds(left, top, right, (int) rectBottom);
+            dnaTop.draw(canvas);
+        }
+
+        // The last busy bit is a drawable that is round on the bottom
+        int lastIndex = busyBits.length - 1;
+        if (busyBits[lastIndex] == 1) {
+            float rectTop = bottom - slotHeight;
+            dnaBottom.setBounds(left, (int) rectTop, right, bottom);
+            dnaBottom.draw(canvas);
+        }
+
+        // Draw all intermediate pieces. We could further optimize this to
+        // draw runs of bits, but it probably won't yield much more performance.
+        float rectTop = top + slotHeight;
+        for (int index = 1; index < lastIndex; index++) {
+            float rectBottom = rectTop + slotHeight;
+            if (busyBits[index] == 1) {
+                dnaMiddle.setBounds(left, (int) rectTop, right, (int) rectBottom);
+                dnaMiddle.draw(canvas);
+            }
+            rectTop = rectBottom;
+        }
+        return bitmap;
+    }
+
+    private void drawEvents(int date, Canvas canvas, Rect rect, Paint p) {
+        // These are the coordinates of the upper left corner where we'll draw the event bitmap
+        int top = rect.top + BUSYBIT_TOP_BOTTOM_MARGIN;
+        int right = rect.right - BUSYBIT_RIGHT_MARGIN;
+        int left = right - BUSYBIT_WIDTH;
+
+        // Display the busy bits.  Draw a rectangle for each run of 1-bits.
+        int day = date - mFirstJulianDay;
+        byte[] busyBits = mBusyBits[day];
+        int lastIndex = busyBits.length - 1;
+
+        // Cache index is simply all of the bits combined into an integer
+        int cacheIndex = 0;
+        for (int i = 0 ; i <= lastIndex; i++) cacheIndex |= busyBits[i] << i;
+        Bitmap bitmap = mEventBitmapCache.get(cacheIndex);
+        if (bitmap == null) {
+            // Create a bitmap that we'll reuse for all events with the same
+            // combination of busyBits.
+            bitmap = createEventBitmap(busyBits, rect);
+            mEventBitmapCache.put(cacheIndex, bitmap);
+        }
+        canvas.drawBitmap(bitmap, left, top, p);
+    }
+
+    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.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 - 25.0f * HOUR_GAP) / 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(mContext)) {
+                    flags |= DateUtils.FORMAT_24HOUR;
+                }
+            }
+
+            String timeRange;
+            if (showEndTime) {
+                timeRange = DateUtils.formatDateRange(event.startMillis, event.endMillis, flags);
+            } else {
+                timeRange = DateUtils.formatDateRange(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);
+                mParentActivity.finish();
+            } 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);
+            mParentActivity.finish();
+            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);
+        } 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);
+        }
+    }
+}
diff --git a/src/com/android/calendar/Navigator.java b/src/com/android/calendar/Navigator.java
new file mode 100644 (file)
index 0000000..66ef2bb
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 android.pim.Time;
+
+public interface Navigator {
+    /**
+     * 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.
+     */
+    void goTo(Time time);
+    
+    /**
+     * 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();
+}
diff --git a/src/com/android/calendar/SelectCalendarsActivity.java b/src/com/android/calendar/SelectCalendarsActivity.java
new file mode 100644 (file)
index 0000000..25751e1
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * 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.app.AlertDialog;
+import android.content.AsyncQueryHandler;
+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;
+import android.os.Bundle;
+import android.provider.Calendar.Calendars;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.AdapterView;
+import android.widget.CheckBox;
+import android.widget.ListView;
+
+
+public class SelectCalendarsActivity extends Activity implements ListView.OnItemClickListener {
+
+    private static final String TAG = "Calendar";
+    private View mView = null;
+    private Cursor mCursor = null;
+    private QueryHandler mQueryHandler;
+    private SelectCalendarsAdapter mAdapter;
+    private static final String[] PROJECTION = new String[] {
+        Calendars._ID,
+        Calendars.DISPLAY_NAME,
+        Calendars.COLOR,
+        Calendars.SELECTED,
+        Calendars.SYNC_EVENTS
+    };
+
+    @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);
+        mQueryHandler = new QueryHandler(getContentResolver());
+        mView = findViewById(R.id.calendars);
+        ListView items = (ListView) mView.findViewById(R.id.items);
+        Context context = mView.getContext();
+        mCursor = managedQuery(Calendars.CONTENT_URI, PROJECTION,
+                Calendars.SYNC_EVENTS + "=1",
+                null /* selectionArgs */,
+                Calendars.DEFAULT_SORT_ORDER);
+                                     
+        mAdapter = new SelectCalendarsAdapter(context, mCursor);
+        items.setAdapter(mAdapter);
+        items.setOnItemClickListener(this);
+        
+        // Start a background sync to get the list of calendars from the server.
+        startCalendarSync();
+    }
+    
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        CheckBox box = (CheckBox) view.findViewById(R.id.checkbox);
+        box.toggle();
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        MenuItem item;
+        item = menu.add(0, 0, 0, R.string.add_calendars)
+                .setOnMenuItemClickListener(new ChangeCalendarAction(false /* not remove */));
+        item.setIcon(android.R.drawable.ic_menu_add);
+        
+        item = menu.add(0, 0, 0, R.string.remove_calendars)
+                .setOnMenuItemClickListener(new ChangeCalendarAction(true /* remove */));
+        item.setIcon(android.R.drawable.ic_menu_delete);
+        return true;
+    }
+
+    /**
+     * ChangeCalendarAction is used both for adding and removing calendars.
+     * The constructor takes a boolean argument that is false if adding
+     * calendars and true if removing calendars.  The user selects calendars
+     * to be added or removed from a pop-up list. 
+     */
+    public class ChangeCalendarAction implements OnMenuItemClickListener,
+            DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
+        
+        int mNumItems;
+        long[] mCalendarIds;
+        boolean[] mIsChecked;
+        ContentResolver mContentResolver;
+        boolean mRemove;
+        
+        public ChangeCalendarAction(boolean remove) {
+            mContentResolver = SelectCalendarsActivity.this.getContentResolver();
+            mRemove = remove;
+        }
+
+        /*
+         * This is called when the user selects a calendar from either the
+         * "Add calendars" or "Remove calendars" popup dialog. 
+         */
+        public void onClick(DialogInterface dialog, int position, boolean isChecked) {
+            mIsChecked[position] = isChecked;
+        }
+
+        /*
+         * This is called when the user presses the OK or Cancel button on the
+         * "Add calendars" or "Remove calendars" popup dialog. 
+         */
+        public void onClick(DialogInterface dialog, int which) {
+            // If the user cancelled the dialog, then do nothing.
+            if (which == DialogInterface.BUTTON2) {
+                return;
+            }
+            
+            boolean changesFound = false;
+            for (int position = 0; position < mNumItems; position++) {
+                // If this calendar wasn't selected, then skip it.
+                if (!mIsChecked[position]) {
+                    continue;
+                }
+                changesFound = true;
+                
+                long id = mCalendarIds[position];
+                Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
+                ContentValues values = new ContentValues();
+                int selected = 1;
+                if (mRemove) {
+                    selected = 0;
+                }
+                values.put(Calendars.SELECTED, selected);
+                values.put(Calendars.SYNC_EVENTS, selected);
+                mContentResolver.update(uri, values, null, null);
+            }
+            
+            // If there were any changes, then update the list of calendars
+            // that are synced.
+            if (changesFound) {
+                mCursor.requery();
+            }
+        }
+        
+        public boolean onMenuItemClick(MenuItem item) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(SelectCalendarsActivity.this);
+            String selection;
+            if (mRemove) {
+                builder.setTitle(R.string.remove_calendars)
+                    .setIcon(android.R.drawable.ic_dialog_alert);
+                selection = Calendars.SYNC_EVENTS + "=1";
+            } else {
+                builder.setTitle(R.string.add_calendars);
+                selection = Calendars.SYNC_EVENTS + "=0";
+            }
+            ContentResolver cr = getContentResolver();
+            Cursor cursor = cr.query(Calendars.CONTENT_URI, PROJECTION,
+                    selection, null /* selectionArgs */,
+                    Calendars.DEFAULT_SORT_ORDER);
+            if (cursor == null) {
+                Log.w(TAG, "Cannot get cursor for calendars");
+                return true;
+            }
+
+            int count = cursor.getCount();
+            mNumItems = count;
+            CharSequence[] calendarNames = new CharSequence[count];
+            mCalendarIds = new long[count];
+            mIsChecked = new boolean[count];
+            try {
+                int pos = 0;
+                while (cursor.moveToNext()) {
+                    mCalendarIds[pos] = cursor.getLong(0);
+                    calendarNames[pos] = cursor.getString(1);
+                    pos += 1;
+                }
+            } finally {
+                cursor.close();
+            }
+            
+            builder.setMultiChoiceItems(calendarNames, null, this)
+                .setPositiveButton(R.string.ok_label, this)
+                .setNegativeButton(R.string.cancel_label, this)
+                .show();
+            return true;
+        }
+    }
+    
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+                    Window.PROGRESS_VISIBILITY_OFF);
+
+            // If the Activity is finishing, then close the cursor.
+            // Otherwise, use the new cursor in the adapter.
+            if (isFinishing()) {
+                stopManagingCursor(cursor);
+                cursor.close();
+            } else {
+                if (cursor.getCount() == 0) {
+                    // There are no calendars.  This might happen if we lost
+                    // the wireless connection (in airplane mode, for example).
+                    // Leave the current list of calendars alone and pop up
+                    // a dialog explaining that the connection is down.
+                    // But allow the user to add and remove calendars.
+                    return;
+                }
+                if (mCursor != null) {
+                    stopManagingCursor(mCursor);
+                }
+                mCursor = cursor;
+                startManagingCursor(cursor);
+                mAdapter.changeCursor(cursor);
+            }
+        }
+    }
+
+    // This class implements the menu option "Refresh list from server".
+    // (No longer used.)
+    public class RefreshAction implements Runnable {
+        public void run() {
+            startCalendarSync();
+        }
+    }
+    
+    // startCalendarSync() checks the server for an updated list of Calendars
+    // (in the background) using an AsyncQueryHandler.
+    //
+    // Calendars are never removed from the phone due to a server sync.
+    // But 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 asynchronous query 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 startCalendarSync() {
+        getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+                Window.PROGRESS_VISIBILITY_ON);
+
+        // TODO: make sure the user has login info.
+        
+        Uri uri = Calendars.LIVE_CONTENT_URI;
+        mQueryHandler.startQuery(0, null, uri, PROJECTION,
+                Calendars.SYNC_EVENTS + "=1",
+                null, Calendars.DEFAULT_SORT_ORDER);
+    }
+}
diff --git a/src/com/android/calendar/SelectCalendarsAdapter.java b/src/com/android/calendar/SelectCalendarsAdapter.java
new file mode 100644 (file)
index 0000000..fb50662
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.net.Uri;
+import android.provider.Calendar.Calendars;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CursorAdapter;
+import android.widget.TextView;
+
+public class SelectCalendarsAdapter extends CursorAdapter {
+
+    private static final int CLEAR_ALPHA_MASK = 0x00FFFFFF;
+    private static final int HIGH_ALPHA = 255 << 24;
+    private static final int MED_ALPHA = 180 << 24;
+    private static final int LOW_ALPHA = 150 << 24;
+
+    /* 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 String TAG = "Calendar";
+
+    private final LayoutInflater mInflater;
+    private final ContentResolver mResolver;
+    private final ContentValues mValues = new ContentValues();
+
+    private class CheckBoxListener implements CheckBox.OnCheckedChangeListener {
+        private final long mCalendarId;
+
+        private CheckBoxListener(long calendarId) {
+            mCalendarId = calendarId;
+        }
+        
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, mCalendarId);
+            mValues.clear();
+            int checked = isChecked ? 1 : 0;
+            mValues.put(Calendars.SELECTED, checked);
+            mResolver.update(uri, mValues, null, null);
+        }
+    }
+
+    public SelectCalendarsAdapter(Context context, Cursor cursor) {
+        super(context, cursor);
+        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return mInflater.inflate(R.layout.calendar_item, parent, false);
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        int idColumn = cursor.getColumnIndexOrThrow(Calendars._ID);
+        int nameColumn = cursor.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
+        int selectedColumn = cursor.getColumnIndexOrThrow(Calendars.SELECTED);
+        int colorColumn = cursor.getColumnIndexOrThrow(Calendars.COLOR);
+        view.findViewById(R.id.color).setBackgroundDrawable(getColorChip(cursor.getInt(colorColumn)));
+        setText(view, R.id.calendar, cursor.getString(nameColumn));
+        CheckBox box = (CheckBox) view.findViewById(R.id.checkbox);
+        long id = cursor.getLong(idColumn);
+        boolean checked = cursor.getInt(selectedColumn) != 0;
+        box.setOnCheckedChangeListener(null);
+        box.setChecked(checked);
+        box.setOnCheckedChangeListener(new CheckBoxListener(id));
+    }
+
+    private static void setText(View view, int id, String text) {
+        if (TextUtils.isEmpty(text)) {
+            return;
+        }
+        TextView textView = (TextView) view.findViewById(id);
+        textView.setText(text);
+    }
+    
+    private 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.
+         */
+        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};
+        GradientDrawable d = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, colors);
+        d.setCornerRadii(CORNERS);
+        return d;
+    }
+}
diff --git a/src/com/android/calendar/Utils.java b/src/com/android/calendar/Utils.java
new file mode 100644 (file)
index 0000000..a261ca8
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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 android.content.Context;
+import android.content.Intent;
+import android.pim.Time;
+import android.view.animation.AlphaAnimation;
+import android.widget.ViewFlipper;
+
+public class Utils {
+    public static void startActivity(Context context, String className, long time) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+
+        intent.setClassName(context, className);
+        intent.putExtra(EVENT_BEGIN_TIME, time);
+
+        context.startActivity(intent);
+    }
+    
+    public static final Time timeFromIntent(Intent intent) {
+        Time time = new Time();
+        time.set(timeFromIntentInMillis(intent));
+        return time;
+    }
+
+    /**
+     * If the given intent specifies a time (in milliseconds since the epoch),
+     * 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.
+        long millis = intent.getLongExtra(EVENT_BEGIN_TIME, -1);
+        if (millis == -1) {
+            millis = System.currentTimeMillis();
+        }
+        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);
+    }
+    
+    /**
+     * Formats the given Time object so that it gives the day of the week
+     * and the date (for example, "Monday, September 3, 2007").  If the
+     * abbrev argument is true, then abbreviated names will be used (for
+     * example, "Mon, Sep 3, 2007").
+     * 
+     * @param time the time to format
+     * @param abbrev if true, use abbreviations for the weekday and month
+     * @return the string containing the weekday and the date 
+     */
+    public static String formatDayDate(Time time, boolean abbrev) {
+        String date;
+        if (abbrev) {
+            date = time.format("%a, %b %-d, %Y");
+        } else {
+            date = time.format("%A, %B %-d, %Y");
+        }
+        return date;
+    }
+    
+    /**
+     * 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 
+     */
+    public static String formatMonthYear(Time time) {
+        return time.format("%B %Y");
+    }
+    
+    // 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];
+    }
+    
+    /**
+     * 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;
+    }
+}
diff --git a/src/com/android/calendar/WeekActivity.java b/src/com/android/calendar/WeekActivity.java
new file mode 100644 (file)
index 0000000..0e06b51
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+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.FILL_PARENT, LayoutParams.FILL_PARENT));
+        wv.setSelectedDay(mSelectedDay);
+        return wv;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        CalendarView view1 = (CalendarView) mViewSwitcher.getCurrentView();
+        CalendarView view2 = (CalendarView) mViewSwitcher.getNextView();
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(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
+        String activityString = CalendarApplication.ACTIVITY_NAMES[CalendarApplication.WEEK_VIEW_ID];
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(CalendarPreferenceActivity.KEY_START_VIEW, activityString);
+        editor.commit();
+    }
+
+    @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
new file mode 100644 (file)
index 0000000..b0b3244
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 = false;
+        mNumDays = 7;
+        mEventGeometry.setCellMargin(CELL_MARGIN);
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644 (file)
index 0000000..28a573f
--- /dev/null
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CalendarTests
+
+LOCAL_INSTRUMENTATION_FOR := Calendar
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..2558ec2
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.calendar.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--
+    The test delcared in this instrumentation will be run along with tests declared by
+    all other applications via the command: "adb shell itr".
+    The "itr" command will find all tests declared by all applications. If you want to run just these
+    tests on their own then use the command:
+    "adb shell am instrument -w com.android.calendar.tests/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.calendar"
+                     android:label="calendar tests"/>
+
+    <instrumentation android:name="CalendarLaunchPerformance"
+        android:targetPackage="com.android.calendar"
+        android:label="Calendar Launch Performance">
+    </instrumentation>  
+
+</manifest>
diff --git a/tests/src/com/android/calendar/CalendarLaunchPerformance.java b/tests/src/com/android/calendar/CalendarLaunchPerformance.java
new file mode 100644 (file)
index 0000000..975f6af
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.tests;
+
+import android.app.Activity;
+import android.test.LaunchPerformanceBase;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * Instrumentation class for Browser launch performance testing.
+ */
+public class CalendarLaunchPerformance extends LaunchPerformanceBase {
+    public static final String LOG_TAG = "CalendarLaunchPerformance";
+
+    public CalendarLaunchPerformance() {
+        super();
+    }
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+
+        mIntent.setClassName(getTargetContext(), "com.android.calendar.LaunchActivity");
+        start();
+    }
+
+    /**
+     * Calls LaunchApp and finish.
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+        LaunchApp();
+        finish(Activity.RESULT_OK, mResults);
+    }
+}
diff --git a/tests/src/com/android/calendar/CalendarTests.java b/tests/src/com/android/calendar/CalendarTests.java
new file mode 100644 (file)
index 0000000..222a64a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.test.TestBrowserActivity;
+import junit.framework.TestSuite;
+
+
+/**
+ * Unit tests for com.android.calendar.
+ */
+public class CalendarTests extends TestBrowserActivity {
+
+    @Override
+    public final TestSuite getTopTestSuite() {
+        return suite();
+    }
+
+    public static TestSuite suite() {
+        TestSuite suite = new TestSuite(CalendarTests.class.getName());
+        suite.addTestSuite(FormatDateRangeTest.class);
+        suite.addTestSuite(WeekNumberTest.class);
+        return suite;
+    }
+}
diff --git a/tests/src/com/android/calendar/FormatDateRangeTest.java b/tests/src/com/android/calendar/FormatDateRangeTest.java
new file mode 100644 (file)
index 0000000..a1cb2b7
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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.res.Resources;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+
+/**
+ * Unit tests for {@link android.pim.DateUtils#formatDateRange}.
+ */
+public class FormatDateRangeTest extends AndroidTestCase {
+
+    private class DateRange {
+        public Time date1;
+        public Time date2;
+        public int flags;
+        public String expectedOutput;
+
+        public DateRange(int year1, int month1, int day1, int hour1, int minute1,
+                int year2, int month2, int day2, int hour2, int minute2,
+                int flags, String output) {
+            if ((flags & DateUtils.FORMAT_UTC) != 0) {
+                date1 = new Time(Time.TIMEZONE_UTC);
+                date2 = new Time(Time.TIMEZONE_UTC);
+            } else {
+                date1 = new Time();
+                date2 = new Time();
+            }
+
+            // If the year is zero, then set it to the current year.
+            if (year1 == 0 && year2 == 0) {
+                date1.set(System.currentTimeMillis());
+                year1 = year2 = date1.year;
+            }
+
+            date1.set(0, minute1, hour1, day1, month1, year1);
+            date1.normalize(true /* ignore isDst */);
+
+            date2.set(0, minute2, hour2, day2, month2, year2);
+            date2.normalize(true /* ignore isDst */);
+
+            this.flags = flags;
+            expectedOutput = output;
+        }
+    }
+
+    private Resources mResources;
+
+    DateRange[] tests = {
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 11, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8am \u2013 11am"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 11, 0,
+                    DateUtils.FORMAT_SHOW_TIME, "8:00am \u2013 11:00am"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 17, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR, "08:00 \u2013 17:00"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 12, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8am \u2013 noon"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 12, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_ABBREV_ALL,
+                    "8am \u2013 12pm"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 9, 12, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON | DateUtils.FORMAT_ABBREV_ALL,
+                    "8am \u2013 Noon"),
+            new DateRange(0, 10, 9, 10, 30, 0, 10, 9, 13, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "10:30am \u2013 1pm"),
+            new DateRange(0, 10, 9, 13, 0, 0, 10, 9, 14, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "1pm \u2013 2pm"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 9, 14, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 2pm"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "8pm \u2013 midnight"),
+            new DateRange(0, 10, 10, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+                    "20:00 \u2013 00:00"),
+            new DateRange(0, 10, 10, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+                    "00:00"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+            new DateRange(0, 10, 10, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 10"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+                    "Nov 9"),
+            new DateRange(0, 10, 10, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+                    "Nov 10"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NO_MIDNIGHT | DateUtils.FORMAT_ABBREV_ALL,
+                    "8pm \u2013 12am"),
+            new DateRange(0, 10, 9, 20, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_MIDNIGHT | DateUtils.FORMAT_ABBREV_ALL,
+                    "8pm \u2013 Midnight"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, "12am \u2013 midnight"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR | DateUtils.FORMAT_ABBREV_ALL,
+                    "00:00 \u2013 00:00"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_UTC, "November 9"),
+            new DateRange(0, 10, 8, 0, 0, 0, 10, 10, 0, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 8 \u2013 9"),
+            new DateRange(0, 10, 9, 0, 0, 0, 10, 11, 0, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9 \u2013 10"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 11, 17, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Nov 9 \u2013 11"),
+            new DateRange(0, 9, 29, 8, 0, 0, 10, 3, 17, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Oct 29 \u2013 Nov 3"),
+            new DateRange(2007, 11, 29, 8, 0, 2008, 0, 2, 17, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Dec 29, 2007 \u2013 Jan 2, 2008"),
+            new DateRange(2007, 11, 29, 0, 0, 2008, 0, 2, 0, 0,
+                    DateUtils.FORMAT_UTC | DateUtils.FORMAT_ABBREV_ALL, "Dec 29, 2007 \u2013 Jan 1, 2008"),
+            new DateRange(2007, 11, 29, 8, 0, 2008, 0, 2, 17, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
+                    "Dec 29, 2007, 8am \u2013 Jan 2, 2008, 5pm"),
+            new DateRange(0, 10, 9, 8, 0, 0, 10, 11, 17, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
+                    "Nov 9, 8am \u2013 Nov 11, 5pm"),
+            new DateRange(2007, 10, 9, 8, 0, 2007, 10, 11, 17, 0,
+                    DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL,
+                    "Fri, Nov 9, 2007 \u2013 Sun, Nov 11, 2007"),
+            new DateRange(2007, 10, 9, 8, 0, 2007, 10, 11, 17, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL,
+                    "Fri, Nov 9, 2007, 8am \u2013 Sun, Nov 11, 2007, 5pm"),
+            new DateRange(2007, 11, 3, 13, 0, 2007, 11, 3, 14, 0,
+                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR,
+                    "1:00pm \u2013 2:00pm, December 3, 2007"),
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mResources = mContext.getResources();
+    }
+
+    public void testAll() throws Exception {
+        int len = tests.length;
+        for (int index = 0; index < len; index++) {
+            DateRange dateRange = tests[index];
+            long startMillis = dateRange.date1.toMillis(false /* use isDst */);
+            long endMillis = dateRange.date2.toMillis(false /* use isDst */);
+            int flags = dateRange.flags;
+            String output = DateUtils.formatDateRange(startMillis, endMillis, flags);
+            if (!dateRange.expectedOutput.equals(output)) {
+                Log.i("FormatDateRangeTest", "index " + index
+                        + " expected: " + dateRange.expectedOutput
+                        + " actual: " + output);
+            }
+            assertEquals(dateRange.expectedOutput, output);
+        }
+    }
+}
diff --git a/tests/src/com/android/calendar/WeekNumberTest.java b/tests/src/com/android/calendar/WeekNumberTest.java
new file mode 100644 (file)
index 0000000..c7af873
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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.res.Resources;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+
+/**
+ * Unit tests for {@link android.pim.Time#getWeekNumber}.
+ */
+public class WeekNumberTest extends AndroidTestCase {
+
+    private class DateAndWeekNumber {
+        public Time date;
+        public Time allDayDate;
+        public int expectedWeekNumber;
+
+        public DateAndWeekNumber(int year, int month, int day, int expectedWeekNumber) {
+            date = new Time();
+            allDayDate = new Time(Time.TIMEZONE_UTC);
+
+            date.set(0, 0, 0, day, month, year);
+            date.normalize(true /* ignore isDst */);
+
+            allDayDate.set(day, month, year);
+            allDayDate.normalize(true /* ignore isDst */);
+
+            this.expectedWeekNumber = expectedWeekNumber; 
+        }
+    }
+
+    DateAndWeekNumber[] tests = {
+            new DateAndWeekNumber(1998, 11, 28, 53),
+            new DateAndWeekNumber(1998, 11, 29, 53),
+            new DateAndWeekNumber(1998, 11, 30, 53),
+            new DateAndWeekNumber(1998, 11, 31, 53),
+            new DateAndWeekNumber(1999, 0, 1, 53),
+            new DateAndWeekNumber(1999, 0, 2, 53),
+            new DateAndWeekNumber(1999, 0, 3, 53),
+            new DateAndWeekNumber(1999, 0, 4, 1),
+            new DateAndWeekNumber(1999, 0, 10, 1),
+            new DateAndWeekNumber(1999, 0, 20, 3),
+            new DateAndWeekNumber(1999, 0, 30, 4),
+
+            new DateAndWeekNumber(1999, 11, 28, 52),
+            new DateAndWeekNumber(1999, 11, 29, 52),
+            new DateAndWeekNumber(1999, 11, 30, 52),
+            new DateAndWeekNumber(1999, 11, 31, 52),
+            new DateAndWeekNumber(2000, 0, 1, 52),
+            new DateAndWeekNumber(2000, 0, 2, 52),
+            new DateAndWeekNumber(2000, 0, 3, 1),
+            new DateAndWeekNumber(2000, 0, 4, 1),
+            new DateAndWeekNumber(2000, 0, 10, 2),
+            new DateAndWeekNumber(2000, 0, 20, 3),
+            new DateAndWeekNumber(2000, 0, 30, 4),
+
+            new DateAndWeekNumber(2000, 11, 28, 52),
+            new DateAndWeekNumber(2000, 11, 29, 52),
+            new DateAndWeekNumber(2000, 11, 30, 52),
+            new DateAndWeekNumber(2000, 11, 31, 52),
+            new DateAndWeekNumber(2001, 0, 1, 1),
+            new DateAndWeekNumber(2001, 0, 2, 1),
+            new DateAndWeekNumber(2001, 0, 3, 1),
+            new DateAndWeekNumber(2001, 0, 4, 1),
+            new DateAndWeekNumber(2001, 0, 10, 2),
+            new DateAndWeekNumber(2001, 0, 20, 3),
+            new DateAndWeekNumber(2001, 0, 30, 5),
+
+            new DateAndWeekNumber(2001, 11, 28, 52),
+            new DateAndWeekNumber(2001, 11, 29, 52),
+            new DateAndWeekNumber(2001, 11, 30, 52),
+            new DateAndWeekNumber(2001, 11, 31, 1),
+            new DateAndWeekNumber(2002, 0, 1, 1),
+            new DateAndWeekNumber(2002, 0, 2, 1),
+            new DateAndWeekNumber(2002, 0, 3, 1),
+            new DateAndWeekNumber(2002, 0, 4, 1),
+            new DateAndWeekNumber(2002, 0, 10, 2),
+            new DateAndWeekNumber(2002, 0, 20, 3),
+            new DateAndWeekNumber(2002, 0, 30, 5),
+
+            new DateAndWeekNumber(2002, 11, 28, 52),
+            new DateAndWeekNumber(2002, 11, 29, 52),
+            new DateAndWeekNumber(2002, 11, 30, 1),
+            new DateAndWeekNumber(2002, 11, 31, 1),
+            new DateAndWeekNumber(2003, 0, 1, 1),
+            new DateAndWeekNumber(2003, 0, 2, 1),
+            new DateAndWeekNumber(2003, 0, 3, 1),
+            new DateAndWeekNumber(2003, 0, 4, 1),
+            new DateAndWeekNumber(2003, 0, 10, 2),
+            new DateAndWeekNumber(2003, 0, 20, 4),
+            new DateAndWeekNumber(2003, 0, 30, 5),
+
+            new DateAndWeekNumber(2003, 11, 28, 52),
+            new DateAndWeekNumber(2003, 11, 29, 1),
+            new DateAndWeekNumber(2003, 11, 30, 1),
+            new DateAndWeekNumber(2003, 11, 31, 1),
+            new DateAndWeekNumber(2004, 0, 1, 1),
+            new DateAndWeekNumber(2004, 0, 2, 1),
+            new DateAndWeekNumber(2004, 0, 3, 1),
+            new DateAndWeekNumber(2004, 0, 4, 1),
+            new DateAndWeekNumber(2004, 0, 10, 2),
+            new DateAndWeekNumber(2004, 0, 20, 4),
+            new DateAndWeekNumber(2004, 0, 30, 5),
+
+            new DateAndWeekNumber(2004, 0, 1, 1),
+            new DateAndWeekNumber(2004, 1, 1, 5),
+            new DateAndWeekNumber(2004, 2, 1, 10),
+            new DateAndWeekNumber(2004, 3, 1, 14),
+            new DateAndWeekNumber(2004, 4, 1, 18),
+            new DateAndWeekNumber(2004, 5, 1, 23),
+            new DateAndWeekNumber(2004, 6, 1, 27),
+            new DateAndWeekNumber(2004, 7, 1, 31),
+            new DateAndWeekNumber(2004, 8, 1, 36),
+            new DateAndWeekNumber(2004, 9, 1, 40),
+            new DateAndWeekNumber(2004, 10, 1, 45),
+            new DateAndWeekNumber(2004, 11, 1, 49),
+
+            new DateAndWeekNumber(2004, 11, 28, 53),
+            new DateAndWeekNumber(2004, 11, 29, 53),
+            new DateAndWeekNumber(2004, 11, 30, 53),
+            new DateAndWeekNumber(2004, 11, 31, 53),
+            new DateAndWeekNumber(2005, 0, 1, 53),
+            new DateAndWeekNumber(2005, 0, 2, 53),
+            new DateAndWeekNumber(2005, 0, 3, 1),
+            new DateAndWeekNumber(2005, 0, 4, 1),
+            new DateAndWeekNumber(2005, 0, 10, 2),
+            new DateAndWeekNumber(2005, 0, 20, 3),
+            new DateAndWeekNumber(2005, 0, 30, 4),
+
+            new DateAndWeekNumber(2005, 11, 28, 52),
+            new DateAndWeekNumber(2005, 11, 29, 52),
+            new DateAndWeekNumber(2005, 11, 30, 52),
+            new DateAndWeekNumber(2005, 11, 31, 52),
+            new DateAndWeekNumber(2006, 0, 1, 52),
+            new DateAndWeekNumber(2006, 0, 2, 1),
+            new DateAndWeekNumber(2006, 0, 3, 1),
+            new DateAndWeekNumber(2006, 0, 4, 1),
+            new DateAndWeekNumber(2006, 0, 10, 2),
+            new DateAndWeekNumber(2006, 0, 20, 3),
+            new DateAndWeekNumber(2006, 0, 30, 5),
+
+            new DateAndWeekNumber(2006, 11, 28, 52),
+            new DateAndWeekNumber(2006, 11, 29, 52),
+            new DateAndWeekNumber(2006, 11, 30, 52),
+            new DateAndWeekNumber(2006, 11, 31, 52),
+            new DateAndWeekNumber(2007, 0, 1, 1),
+            new DateAndWeekNumber(2007, 0, 2, 1),
+            new DateAndWeekNumber(2007, 0, 3, 1),
+            new DateAndWeekNumber(2007, 0, 4, 1),
+            new DateAndWeekNumber(2007, 0, 10, 2),
+            new DateAndWeekNumber(2007, 0, 20, 3),
+            new DateAndWeekNumber(2007, 0, 30, 5),
+
+            new DateAndWeekNumber(2007, 11, 28, 52),
+            new DateAndWeekNumber(2007, 11, 29, 52),
+            new DateAndWeekNumber(2007, 11, 30, 52),
+            new DateAndWeekNumber(2007, 11, 31, 1),
+            new DateAndWeekNumber(2008, 0, 1, 1),
+            new DateAndWeekNumber(2008, 0, 2, 1),
+            new DateAndWeekNumber(2008, 0, 3, 1),
+            new DateAndWeekNumber(2008, 0, 4, 1),
+            new DateAndWeekNumber(2008, 0, 10, 2),
+            new DateAndWeekNumber(2008, 0, 20, 3),
+            new DateAndWeekNumber(2008, 0, 30, 5),
+
+            new DateAndWeekNumber(2008, 11, 28, 52),
+            new DateAndWeekNumber(2008, 11, 29, 1),
+            new DateAndWeekNumber(2008, 11, 30, 1),
+            new DateAndWeekNumber(2008, 11, 31, 1),
+            new DateAndWeekNumber(2009, 0, 1, 1),
+            new DateAndWeekNumber(2009, 0, 2, 1),
+            new DateAndWeekNumber(2009, 0, 3, 1),
+            new DateAndWeekNumber(2009, 0, 4, 1),
+            new DateAndWeekNumber(2009, 0, 10, 2),
+            new DateAndWeekNumber(2009, 0, 20, 4),
+            new DateAndWeekNumber(2009, 0, 30, 5),
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testAll() throws Exception {
+        int len = tests.length;
+        for (int index = 0; index < len; index++) {
+            DateAndWeekNumber test = tests[index];
+            int weekNumber = test.date.getWeekNumber();
+            if (weekNumber != test.expectedWeekNumber) {
+                long millis = test.date.toMillis(false /* use isDst */);
+                int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE;
+                String output = DateUtils.formatDateRange(millis, millis, flags);
+                Log.i("WeekNumberTest", "index " + index
+                        + " date: " + output
+                        + " expected: " + test.expectedWeekNumber
+                        + " actual: " + weekNumber);
+            }
+            assertEquals(weekNumber, test.expectedWeekNumber);
+
+            weekNumber = test.allDayDate.getWeekNumber();
+            if (weekNumber != test.expectedWeekNumber) {
+                long millis = test.date.toMillis(false /* use isDst */);
+                int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE;
+                String output = DateUtils.formatDateRange(millis, millis, flags);
+                Log.i("WeekNumberTest", "(all-day) index " + index
+                        + " date: " + output
+                        + " expected: " + test.expectedWeekNumber
+                        + " actual: " + weekNumber);
+            }
+            assertEquals(weekNumber, test.expectedWeekNumber);
+        }
+    }
+}