summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk17
-rw-r--r--AndroidManifest.xml97
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE190
-rw-r--r--res/anim/slide_down_in.xml19
-rw-r--r--res/anim/slide_down_out.xml19
-rw-r--r--res/anim/slide_left_in.xml19
-rw-r--r--res/anim/slide_left_out.xml19
-rw-r--r--res/anim/slide_right_in.xml19
-rw-r--r--res/anim/slide_right_out.xml19
-rw-r--r--res/anim/slide_up_in.xml19
-rw-r--r--res/anim/slide_up_out.xml19
-rw-r--r--res/drawable-land/dna_1_of_6.pngbin0 -> 212 bytes
-rw-r--r--res/drawable-land/dna_2345_of_6.pngbin0 -> 182 bytes
-rw-r--r--res/drawable-land/dna_6_of_6.pngbin0 -> 226 bytes
-rw-r--r--res/drawable-land/dna_empty.pngbin0 -> 452 bytes
-rw-r--r--res/drawable/angenda_item.xml22
-rw-r--r--res/drawable/app_calendar.pngbin0 -> 2292 bytes
-rw-r--r--res/drawable/app_icon.pngbin0 -> 3288 bytes
-rw-r--r--res/drawable/box_appointment.xml21
-rw-r--r--res/drawable/box_appointment_longpress.9.pngbin0 -> 339 bytes
-rw-r--r--res/drawable/box_appointment_normal.9.pngbin0 -> 421 bytes
-rw-r--r--res/drawable/box_appointment_pressed.9.pngbin0 -> 339 bytes
-rw-r--r--res/drawable/box_appointment_selected.9.pngbin0 -> 392 bytes
-rw-r--r--res/drawable/box_color_white.9.pngbin0 -> 2976 bytes
-rw-r--r--res/drawable/calendars_item.xml22
-rw-r--r--res/drawable/dna_1_of_6.pngbin0 -> 262 bytes
-rw-r--r--res/drawable/dna_2345_of_6.pngbin0 -> 220 bytes
-rw-r--r--res/drawable/dna_6_of_6.pngbin0 -> 264 bytes
-rw-r--r--res/drawable/dna_empty.pngbin0 -> 582 bytes
-rw-r--r--res/drawable/ic_alarm_dark.pngbin0 -> 288 bytes
-rw-r--r--res/drawable/ic_alarm_white.pngbin0 -> 297 bytes
-rw-r--r--res/drawable/ic_menu_reminder.pngbin0 -> 4766 bytes
-rw-r--r--res/drawable/ic_menu_show_list.pngbin0 -> 1342 bytes
-rw-r--r--res/drawable/ic_repeat_dark.pngbin0 -> 439 bytes
-rw-r--r--res/drawable/ic_repeat_white.pngbin0 -> 363 bytes
-rw-r--r--res/drawable/ic_statusbar_calendar.pngbin0 -> 728 bytes
-rw-r--r--res/drawable/line.xml23
-rw-r--r--res/drawable/month_view_background.9.pngbin0 -> 3105 bytes
-rw-r--r--res/drawable/month_view_longpress.9.pngbin0 -> 876 bytes
-rw-r--r--res/drawable/month_view_pressed.9.pngbin0 -> 813 bytes
-rw-r--r--res/drawable/month_view_selected.9.pngbin0 -> 546 bytes
-rw-r--r--res/drawable/month_view_today_background.9.pngbin0 -> 3106 bytes
-rw-r--r--res/drawable/panel_section_divider.9.pngbin0 -> 2962 bytes
-rw-r--r--res/drawable/round_rect.xml23
-rw-r--r--res/drawable/section_divider.9.pngbin0 -> 2962 bytes
-rw-r--r--res/drawable/selection.xml27
-rwxr-xr-xres/drawable/stat_notify_calendar.pngbin0 -> 533 bytes
-rw-r--r--res/drawable/sym_calendar_event.pngbin0 -> 130 bytes
-rw-r--r--res/drawable/title_left_arrow.pngbin0 -> 2866 bytes
-rw-r--r--res/drawable/title_right_arrow.pngbin0 -> 2862 bytes
-rw-r--r--res/layout/agenda_activity.xml25
-rw-r--r--res/layout/agenda_day.xml56
-rw-r--r--res/layout/agenda_item.xml95
-rw-r--r--res/layout/agenda_reminder_item.xml36
-rw-r--r--res/layout/alert_activity.xml52
-rw-r--r--res/layout/alert_item.xml97
-rw-r--r--res/layout/alert_toast.xml91
-rw-r--r--res/layout/bubble_event.xml63
-rw-r--r--res/layout/calendar_item.xml41
-rw-r--r--res/layout/calendars_activity.xml26
-rw-r--r--res/layout/calendars_dropdown_item.xml34
-rw-r--r--res/layout/calendars_item.xml32
-rw-r--r--res/layout/day_activity.xml58
-rw-r--r--res/layout/edit_event.xml344
-rw-r--r--res/layout/edit_reminder_item.xml39
-rw-r--r--res/layout/event_activity.xml433
-rw-r--r--res/layout/event_info_activity.xml213
-rw-r--r--res/layout/ics_import_activity.xml74
-rw-r--r--res/layout/month_activity.xml86
-rw-r--r--res/layout/month_bubble.xml101
-rw-r--r--res/layout/status_bar_event.xml86
-rw-r--r--res/layout/view_reminder_item.xml36
-rw-r--r--res/layout/week_activity.xml58
-rw-r--r--res/values-cs/strings.xml113
-rw-r--r--res/values-de-rDE/strings.xml124
-rw-r--r--res/values-en-rGB/strings.xml122
-rw-r--r--res/values-es-rUS/strings.xml124
-rw-r--r--res/values-fr-rFR/strings.xml124
-rw-r--r--res/values-it-rIT/strings.xml124
-rw-r--r--res/values-land/integers.xml19
-rw-r--r--res/values-nl-rNL/strings.xml113
-rw-r--r--res/values-zh-rTW/strings.xml124
-rw-r--r--res/values/arrays.xml246
-rw-r--r--res/values/colors.xml80
-rw-r--r--res/values/integers.xml19
-rw-r--r--res/values/strings.xml230
-rw-r--r--res/values/styles.xml99
-rw-r--r--res/xml/preferences.xml55
-rw-r--r--src/com/android/calendar/AgendaActivity.java428
-rw-r--r--src/com/android/calendar/AgendaAdapter.java139
-rw-r--r--src/com/android/calendar/AgendaByDayAdapter.java364
-rw-r--r--src/com/android/calendar/AlertActivity.java286
-rw-r--r--src/com/android/calendar/AlertAdapter.java106
-rw-r--r--src/com/android/calendar/AlertReceiver.java215
-rw-r--r--src/com/android/calendar/AlertService.java391
-rw-r--r--src/com/android/calendar/CalendarActivity.java360
-rw-r--r--src/com/android/calendar/CalendarApplication.java84
-rw-r--r--src/com/android/calendar/CalendarData.java55
-rw-r--r--src/com/android/calendar/CalendarPreferenceActivity.java85
-rw-r--r--src/com/android/calendar/CalendarView.java2999
-rw-r--r--src/com/android/calendar/DateSpinner.java275
-rw-r--r--src/com/android/calendar/DayActivity.java72
-rw-r--r--src/com/android/calendar/DayView.java33
-rw-r--r--src/com/android/calendar/DeleteEventHelper.java306
-rw-r--r--src/com/android/calendar/EditEvent.java1456
-rw-r--r--src/com/android/calendar/Event.java650
-rw-r--r--src/com/android/calendar/EventGeometry.java221
-rw-r--r--src/com/android/calendar/EventInfoActivity.java565
-rw-r--r--src/com/android/calendar/EventLoader.java256
-rw-r--r--src/com/android/calendar/IcsImportActivity.java227
-rw-r--r--src/com/android/calendar/LaunchActivity.java112
-rw-r--r--src/com/android/calendar/MenuHelper.java181
-rw-r--r--src/com/android/calendar/MonthActivity.java341
-rw-r--r--src/com/android/calendar/MonthView.java1351
-rw-r--r--src/com/android/calendar/Navigator.java45
-rw-r--r--src/com/android/calendar/SelectCalendarsActivity.java274
-rw-r--r--src/com/android/calendar/SelectCalendarsAdapter.java125
-rw-r--r--src/com/android/calendar/Utils.java122
-rw-r--r--src/com/android/calendar/WeekActivity.java83
-rw-r--r--src/com/android/calendar/WeekView.java33
-rw-r--r--tests/Android.mk16
-rw-r--r--tests/AndroidManifest.xml41
-rw-r--r--tests/src/com/android/calendar/CalendarLaunchPerformance.java53
-rw-r--r--tests/src/com/android/calendar/CalendarTests.java39
-rw-r--r--tests/src/com/android/calendar/FormatDateRangeTest.java177
-rw-r--r--tests/src/com/android/calendar/WeekNumberTest.java231
127 files changed, 17283 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 00000000..06ac1038
--- /dev/null
+++ b/Android.mk
@@ -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
index 00000000..3b0a1418
--- /dev/null
+++ b/AndroidManifest.xml
@@ -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
index 00000000..e69de29b
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..c5b1efa7
--- /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
index 00000000..dd1ca086
--- /dev/null
+++ b/res/anim/slide_down_in.xml
@@ -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
index 00000000..188c8170
--- /dev/null
+++ b/res/anim/slide_down_out.xml
@@ -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
index 00000000..c72fd87b
--- /dev/null
+++ b/res/anim/slide_left_in.xml
@@ -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
index 00000000..e4308ec0
--- /dev/null
+++ b/res/anim/slide_left_out.xml
@@ -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
index 00000000..f2f97fcd
--- /dev/null
+++ b/res/anim/slide_right_in.xml
@@ -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
index 00000000..1807f107
--- /dev/null
+++ b/res/anim/slide_right_out.xml
@@ -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
index 00000000..675908f7
--- /dev/null
+++ b/res/anim/slide_up_in.xml
@@ -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
index 00000000..a1303a5a
--- /dev/null
+++ b/res/anim/slide_up_out.xml
@@ -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
index 00000000..2f6aa9c2
--- /dev/null
+++ b/res/drawable-land/dna_1_of_6.png
Binary files 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
index 00000000..bfed653b
--- /dev/null
+++ b/res/drawable-land/dna_2345_of_6.png
Binary files 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
index 00000000..cf861b64
--- /dev/null
+++ b/res/drawable-land/dna_6_of_6.png
Binary files differ
diff --git a/res/drawable-land/dna_empty.png b/res/drawable-land/dna_empty.png
new file mode 100644
index 00000000..da09adce
--- /dev/null
+++ b/res/drawable-land/dna_empty.png
Binary files differ
diff --git a/res/drawable/angenda_item.xml b/res/drawable/angenda_item.xml
new file mode 100644
index 00000000..e63e41cb
--- /dev/null
+++ b/res/drawable/angenda_item.xml
@@ -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
index 00000000..5e07a156
--- /dev/null
+++ b/res/drawable/app_calendar.png
Binary files differ
diff --git a/res/drawable/app_icon.png b/res/drawable/app_icon.png
new file mode 100644
index 00000000..92410907
--- /dev/null
+++ b/res/drawable/app_icon.png
Binary files differ
diff --git a/res/drawable/box_appointment.xml b/res/drawable/box_appointment.xml
new file mode 100644
index 00000000..e3d76e79
--- /dev/null
+++ b/res/drawable/box_appointment.xml
@@ -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
index 00000000..b3a63baa
--- /dev/null
+++ b/res/drawable/box_appointment_longpress.9.png
Binary files differ
diff --git a/res/drawable/box_appointment_normal.9.png b/res/drawable/box_appointment_normal.9.png
new file mode 100644
index 00000000..b5c56844
--- /dev/null
+++ b/res/drawable/box_appointment_normal.9.png
Binary files differ
diff --git a/res/drawable/box_appointment_pressed.9.png b/res/drawable/box_appointment_pressed.9.png
new file mode 100644
index 00000000..d05e886a
--- /dev/null
+++ b/res/drawable/box_appointment_pressed.9.png
Binary files differ
diff --git a/res/drawable/box_appointment_selected.9.png b/res/drawable/box_appointment_selected.9.png
new file mode 100644
index 00000000..51808d6d
--- /dev/null
+++ b/res/drawable/box_appointment_selected.9.png
Binary files differ
diff --git a/res/drawable/box_color_white.9.png b/res/drawable/box_color_white.9.png
new file mode 100644
index 00000000..aaaa3812
--- /dev/null
+++ b/res/drawable/box_color_white.9.png
Binary files differ
diff --git a/res/drawable/calendars_item.xml b/res/drawable/calendars_item.xml
new file mode 100644
index 00000000..6520b528
--- /dev/null
+++ b/res/drawable/calendars_item.xml
@@ -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
index 00000000..af524bc5
--- /dev/null
+++ b/res/drawable/dna_1_of_6.png
Binary files differ
diff --git a/res/drawable/dna_2345_of_6.png b/res/drawable/dna_2345_of_6.png
new file mode 100644
index 00000000..b2a22d90
--- /dev/null
+++ b/res/drawable/dna_2345_of_6.png
Binary files differ
diff --git a/res/drawable/dna_6_of_6.png b/res/drawable/dna_6_of_6.png
new file mode 100644
index 00000000..97914c5b
--- /dev/null
+++ b/res/drawable/dna_6_of_6.png
Binary files differ
diff --git a/res/drawable/dna_empty.png b/res/drawable/dna_empty.png
new file mode 100644
index 00000000..257c00aa
--- /dev/null
+++ b/res/drawable/dna_empty.png
Binary files differ
diff --git a/res/drawable/ic_alarm_dark.png b/res/drawable/ic_alarm_dark.png
new file mode 100644
index 00000000..8e0a0cfb
--- /dev/null
+++ b/res/drawable/ic_alarm_dark.png
Binary files differ
diff --git a/res/drawable/ic_alarm_white.png b/res/drawable/ic_alarm_white.png
new file mode 100644
index 00000000..54fa86e1
--- /dev/null
+++ b/res/drawable/ic_alarm_white.png
Binary files differ
diff --git a/res/drawable/ic_menu_reminder.png b/res/drawable/ic_menu_reminder.png
new file mode 100644
index 00000000..23c6936f
--- /dev/null
+++ b/res/drawable/ic_menu_reminder.png
Binary files differ
diff --git a/res/drawable/ic_menu_show_list.png b/res/drawable/ic_menu_show_list.png
new file mode 100644
index 00000000..debced4b
--- /dev/null
+++ b/res/drawable/ic_menu_show_list.png
Binary files differ
diff --git a/res/drawable/ic_repeat_dark.png b/res/drawable/ic_repeat_dark.png
new file mode 100644
index 00000000..9ad78c88
--- /dev/null
+++ b/res/drawable/ic_repeat_dark.png
Binary files differ
diff --git a/res/drawable/ic_repeat_white.png b/res/drawable/ic_repeat_white.png
new file mode 100644
index 00000000..95bef880
--- /dev/null
+++ b/res/drawable/ic_repeat_white.png
Binary files differ
diff --git a/res/drawable/ic_statusbar_calendar.png b/res/drawable/ic_statusbar_calendar.png
new file mode 100644
index 00000000..9539623b
--- /dev/null
+++ b/res/drawable/ic_statusbar_calendar.png
Binary files differ
diff --git a/res/drawable/line.xml b/res/drawable/line.xml
new file mode 100644
index 00000000..9ffd504a
--- /dev/null
+++ b/res/drawable/line.xml
@@ -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
index 00000000..8a8bfcbf
--- /dev/null
+++ b/res/drawable/month_view_background.9.png
Binary files differ
diff --git a/res/drawable/month_view_longpress.9.png b/res/drawable/month_view_longpress.9.png
new file mode 100644
index 00000000..e84b4fa9
--- /dev/null
+++ b/res/drawable/month_view_longpress.9.png
Binary files differ
diff --git a/res/drawable/month_view_pressed.9.png b/res/drawable/month_view_pressed.9.png
new file mode 100644
index 00000000..70c0f945
--- /dev/null
+++ b/res/drawable/month_view_pressed.9.png
Binary files differ
diff --git a/res/drawable/month_view_selected.9.png b/res/drawable/month_view_selected.9.png
new file mode 100644
index 00000000..67a6e2e9
--- /dev/null
+++ b/res/drawable/month_view_selected.9.png
Binary files 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
index 00000000..42e54f0d
--- /dev/null
+++ b/res/drawable/month_view_today_background.9.png
Binary files differ
diff --git a/res/drawable/panel_section_divider.9.png b/res/drawable/panel_section_divider.9.png
new file mode 100644
index 00000000..46098aef
--- /dev/null
+++ b/res/drawable/panel_section_divider.9.png
Binary files differ
diff --git a/res/drawable/round_rect.xml b/res/drawable/round_rect.xml
new file mode 100644
index 00000000..37c9237f
--- /dev/null
+++ b/res/drawable/round_rect.xml
@@ -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
index 00000000..46098aef
--- /dev/null
+++ b/res/drawable/section_divider.9.png
Binary files differ
diff --git a/res/drawable/selection.xml b/res/drawable/selection.xml
new file mode 100644
index 00000000..7ca6b555
--- /dev/null
+++ b/res/drawable/selection.xml
@@ -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
index 00000000..4433a16a
--- /dev/null
+++ b/res/drawable/stat_notify_calendar.png
Binary files differ
diff --git a/res/drawable/sym_calendar_event.png b/res/drawable/sym_calendar_event.png
new file mode 100644
index 00000000..43428a1c
--- /dev/null
+++ b/res/drawable/sym_calendar_event.png
Binary files differ
diff --git a/res/drawable/title_left_arrow.png b/res/drawable/title_left_arrow.png
new file mode 100644
index 00000000..5734b8aa
--- /dev/null
+++ b/res/drawable/title_left_arrow.png
Binary files differ
diff --git a/res/drawable/title_right_arrow.png b/res/drawable/title_right_arrow.png
new file mode 100644
index 00000000..402051a6
--- /dev/null
+++ b/res/drawable/title_right_arrow.png
Binary files differ
diff --git a/res/layout/agenda_activity.xml b/res/layout/agenda_activity.xml
new file mode 100644
index 00000000..4bf0ee61
--- /dev/null
+++ b/res/layout/agenda_activity.xml
@@ -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
index 00000000..0a50ebcf
--- /dev/null
+++ b/res/layout/agenda_day.xml
@@ -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
index 00000000..76386ba3
--- /dev/null
+++ b/res/layout/agenda_item.xml
@@ -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
index 00000000..40383606
--- /dev/null
+++ b/res/layout/agenda_reminder_item.xml
@@ -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
index 00000000..ba0074f8
--- /dev/null
+++ b/res/layout/alert_activity.xml
@@ -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
index 00000000..3f389bd8
--- /dev/null
+++ b/res/layout/alert_item.xml
@@ -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
index 00000000..17b1808f
--- /dev/null
+++ b/res/layout/alert_toast.xml
@@ -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
index 00000000..0c273c67
--- /dev/null
+++ b/res/layout/bubble_event.xml
@@ -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
index 00000000..2fce8c8f
--- /dev/null
+++ b/res/layout/calendar_item.xml
@@ -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
index 00000000..3eb19b17
--- /dev/null
+++ b/res/layout/calendars_activity.xml
@@ -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
index 00000000..5b6bbd6c
--- /dev/null
+++ b/res/layout/calendars_dropdown_item.xml
@@ -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
index 00000000..1ba2d08d
--- /dev/null
+++ b/res/layout/calendars_item.xml
@@ -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
index 00000000..aeadd56e
--- /dev/null
+++ b/res/layout/day_activity.xml
@@ -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
index 00000000..3252b9b3
--- /dev/null
+++ b/res/layout/edit_event.xml
@@ -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
index 00000000..745410d6
--- /dev/null
+++ b/res/layout/edit_reminder_item.xml
@@ -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
index 00000000..08b61022
--- /dev/null
+++ b/res/layout/event_activity.xml
@@ -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
index 00000000..ebdcfc2b
--- /dev/null
+++ b/res/layout/event_info_activity.xml
@@ -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
index 00000000..37427fc2
--- /dev/null
+++ b/res/layout/ics_import_activity.xml
@@ -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
index 00000000..3f287cad
--- /dev/null
+++ b/res/layout/month_activity.xml
@@ -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
index 00000000..6c6c67a7
--- /dev/null
+++ b/res/layout/month_bubble.xml
@@ -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
index 00000000..8eb9b2a7
--- /dev/null
+++ b/res/layout/status_bar_event.xml
@@ -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
index 00000000..957396ca
--- /dev/null
+++ b/res/layout/view_reminder_item.xml
@@ -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
index 00000000..aeadd56e
--- /dev/null
+++ b/res/layout/week_activity.xml
@@ -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
index 00000000..7c0598c8
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -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
index 00000000..a58ee079
--- /dev/null
+++ b/res/values-de-rDE/strings.xml
@@ -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
index 00000000..f206c4f0
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -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
index 00000000..8dde2b91
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -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
index 00000000..bf4fa0b6
--- /dev/null
+++ b/res/values-fr-rFR/strings.xml
@@ -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
index 00000000..a7c46cbf
--- /dev/null
+++ b/res/values-it-rIT/strings.xml
@@ -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
index 00000000..9ece1082
--- /dev/null
+++ b/res/values-land/integers.xml
@@ -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
index 00000000..5d119984
--- /dev/null
+++ b/res/values-nl-rNL/strings.xml
@@ -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
index 00000000..7c8393bc
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -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
index 00000000..89c41e54
--- /dev/null
+++ b/res/values/arrays.xml
@@ -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
index 00000000..b7e3c909
--- /dev/null
+++ b/res/values/colors.xml
@@ -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
index 00000000..ac44790c
--- /dev/null
+++ b/res/values/integers.xml
@@ -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
index 00000000..d4edf8a8
--- /dev/null
+++ b/res/values/strings.xml
@@ -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
index 00000000..5a41f41f
--- /dev/null
+++ b/res/values/styles.xml
@@ -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
index 00000000..061170eb
--- /dev/null
+++ b/res/xml/preferences.xml
@@ -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
index 00000000..40744aab
--- /dev/null
+++ b/src/com/android/calendar/AgendaActivity.java
@@ -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
index 00000000..3ec576be
--- /dev/null
+++ b/src/com/android/calendar/AgendaAdapter.java
@@ -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
index 00000000..a695693b
--- /dev/null
+++ b/src/com/android/calendar/AgendaByDayAdapter.java
@@ -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
index 00000000..52b1a7d7
--- /dev/null
+++ b/src/com/android/calendar/AlertActivity.java
@@ -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
index 00000000..102e9fff
--- /dev/null
+++ b/src/com/android/calendar/AlertAdapter.java
@@ -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
index 00000000..ea2d549f
--- /dev/null
+++ b/src/com/android/calendar/AlertReceiver.java
@@ -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
index 00000000..13f808bf
--- /dev/null
+++ b/src/com/android/calendar/AlertService.java
@@ -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
index 00000000..a99ba61c
--- /dev/null
+++ b/src/com/android/calendar/CalendarActivity.java
@@ -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
index 00000000..a3dad951
--- /dev/null
+++ b/src/com/android/calendar/CalendarApplication.java
@@ -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
index 00000000..49f3056f
--- /dev/null
+++ b/src/com/android/calendar/CalendarData.java
@@ -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
index 00000000..78d60651
--- /dev/null
+++ b/src/com/android/calendar/CalendarPreferenceActivity.java
@@ -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
index 00000000..9126d6eb
--- /dev/null
+++ b/src/com/android/calendar/CalendarView.java
@@ -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
index 00000000..34cc7d5d
--- /dev/null
+++ b/src/com/android/calendar/DateSpinner.java
@@ -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
index 00000000..51f965b8
--- /dev/null
+++ b/src/com/android/calendar/DayActivity.java
@@ -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
index 00000000..a24b51b5
--- /dev/null
+++ b/src/com/android/calendar/DayView.java
@@ -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
index 00000000..7f958b71
--- /dev/null
+++ b/src/com/android/calendar/DeleteEventHelper.java
@@ -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
index 00000000..8c11974b
--- /dev/null
+++ b/src/com/android/calendar/EditEvent.java
@@ -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
index 00000000..d1551bb7
--- /dev/null
+++ b/src/com/android/calendar/Event.java
@@ -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
index 00000000..ebfa6a3e
--- /dev/null
+++ b/src/com/android/calendar/EventGeometry.java
@@ -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
index 00000000..8615c2a3
--- /dev/null
+++ b/src/com/android/calendar/EventInfoActivity.java
@@ -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
index 00000000..eb8a82d7
--- /dev/null
+++ b/src/com/android/calendar/EventLoader.java
@@ -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
index 00000000..c4f5fdea
--- /dev/null
+++ b/src/com/android/calendar/IcsImportActivity.java
@@ -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
index 00000000..d6880597
--- /dev/null
+++ b/src/com/android/calendar/LaunchActivity.java
@@ -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
index 00000000..5ee0be43
--- /dev/null
+++ b/src/com/android/calendar/MenuHelper.java
@@ -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
index 00000000..11fdf5b0
--- /dev/null
+++ b/src/com/android/calendar/MonthActivity.java
@@ -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
index 00000000..5949d956
--- /dev/null
+++ b/src/com/android/calendar/MonthView.java
@@ -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
index 00000000..66ef2bb5
--- /dev/null
+++ b/src/com/android/calendar/Navigator.java
@@ -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
index 00000000..25751e12
--- /dev/null
+++ b/src/com/android/calendar/SelectCalendarsActivity.java
@@ -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
index 00000000..fb50662a
--- /dev/null
+++ b/src/com/android/calendar/SelectCalendarsAdapter.java
@@ -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
index 00000000..a261ca83
--- /dev/null
+++ b/src/com/android/calendar/Utils.java
@@ -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
index 00000000..0e06b518
--- /dev/null
+++ b/src/com/android/calendar/WeekActivity.java
@@ -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
index 00000000..b0b32445
--- /dev/null
+++ b/src/com/android/calendar/WeekView.java
@@ -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
index 00000000..28a573f7
--- /dev/null
+++ b/tests/Android.mk
@@ -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
index 00000000..2558ec2e
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -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
index 00000000..975f6aff
--- /dev/null
+++ b/tests/src/com/android/calendar/CalendarLaunchPerformance.java
@@ -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
index 00000000..222a64a8
--- /dev/null
+++ b/tests/src/com/android/calendar/CalendarTests.java
@@ -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
index 00000000..a1cb2b70
--- /dev/null
+++ b/tests/src/com/android/calendar/FormatDateRangeTest.java
@@ -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
index 00000000..c7af8730
--- /dev/null
+++ b/tests/src/com/android/calendar/WeekNumberTest.java
@@ -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);
+ }
+ }
+}